dm-core 0.9.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 (101) hide show
  1. data/CHANGELOG +144 -0
  2. data/FAQ +74 -0
  3. data/MIT-LICENSE +22 -0
  4. data/QUICKLINKS +12 -0
  5. data/README +143 -0
  6. data/lib/dm-core.rb +213 -0
  7. data/lib/dm-core/adapters.rb +4 -0
  8. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
  10. data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
  11. data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  13. data/lib/dm-core/associations.rb +172 -0
  14. data/lib/dm-core/associations/many_to_many.rb +138 -0
  15. data/lib/dm-core/associations/many_to_one.rb +101 -0
  16. data/lib/dm-core/associations/one_to_many.rb +275 -0
  17. data/lib/dm-core/associations/one_to_one.rb +61 -0
  18. data/lib/dm-core/associations/relationship.rb +116 -0
  19. data/lib/dm-core/associations/relationship_chain.rb +74 -0
  20. data/lib/dm-core/auto_migrations.rb +64 -0
  21. data/lib/dm-core/collection.rb +604 -0
  22. data/lib/dm-core/hook.rb +11 -0
  23. data/lib/dm-core/identity_map.rb +45 -0
  24. data/lib/dm-core/is.rb +16 -0
  25. data/lib/dm-core/logger.rb +233 -0
  26. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  27. data/lib/dm-core/migrator.rb +29 -0
  28. data/lib/dm-core/model.rb +399 -0
  29. data/lib/dm-core/naming_conventions.rb +52 -0
  30. data/lib/dm-core/property.rb +611 -0
  31. data/lib/dm-core/property_set.rb +158 -0
  32. data/lib/dm-core/query.rb +590 -0
  33. data/lib/dm-core/repository.rb +159 -0
  34. data/lib/dm-core/resource.rb +618 -0
  35. data/lib/dm-core/scope.rb +35 -0
  36. data/lib/dm-core/support.rb +7 -0
  37. data/lib/dm-core/support/array.rb +13 -0
  38. data/lib/dm-core/support/assertions.rb +8 -0
  39. data/lib/dm-core/support/errors.rb +23 -0
  40. data/lib/dm-core/support/kernel.rb +7 -0
  41. data/lib/dm-core/support/symbol.rb +41 -0
  42. data/lib/dm-core/transaction.rb +267 -0
  43. data/lib/dm-core/type.rb +160 -0
  44. data/lib/dm-core/type_map.rb +80 -0
  45. data/lib/dm-core/types.rb +19 -0
  46. data/lib/dm-core/types/boolean.rb +7 -0
  47. data/lib/dm-core/types/discriminator.rb +32 -0
  48. data/lib/dm-core/types/object.rb +20 -0
  49. data/lib/dm-core/types/paranoid_boolean.rb +23 -0
  50. data/lib/dm-core/types/paranoid_datetime.rb +22 -0
  51. data/lib/dm-core/types/serial.rb +9 -0
  52. data/lib/dm-core/types/text.rb +10 -0
  53. data/spec/integration/association_spec.rb +1215 -0
  54. data/spec/integration/association_through_spec.rb +150 -0
  55. data/spec/integration/associations/many_to_many_spec.rb +171 -0
  56. data/spec/integration/associations/many_to_one_spec.rb +123 -0
  57. data/spec/integration/associations/one_to_many_spec.rb +66 -0
  58. data/spec/integration/auto_migrations_spec.rb +398 -0
  59. data/spec/integration/collection_spec.rb +1015 -0
  60. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  61. data/spec/integration/model_spec.rb +68 -0
  62. data/spec/integration/mysql_adapter_spec.rb +85 -0
  63. data/spec/integration/postgres_adapter_spec.rb +732 -0
  64. data/spec/integration/property_spec.rb +224 -0
  65. data/spec/integration/query_spec.rb +376 -0
  66. data/spec/integration/repository_spec.rb +57 -0
  67. data/spec/integration/resource_spec.rb +324 -0
  68. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  69. data/spec/integration/sti_spec.rb +185 -0
  70. data/spec/integration/transaction_spec.rb +75 -0
  71. data/spec/integration/type_spec.rb +149 -0
  72. data/spec/lib/mock_adapter.rb +27 -0
  73. data/spec/spec_helper.rb +112 -0
  74. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  75. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  76. data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
  77. data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
  78. data/spec/unit/associations/many_to_many_spec.rb +14 -0
  79. data/spec/unit/associations/many_to_one_spec.rb +138 -0
  80. data/spec/unit/associations/one_to_many_spec.rb +385 -0
  81. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  82. data/spec/unit/associations/relationship_spec.rb +67 -0
  83. data/spec/unit/associations_spec.rb +205 -0
  84. data/spec/unit/auto_migrations_spec.rb +110 -0
  85. data/spec/unit/collection_spec.rb +174 -0
  86. data/spec/unit/data_mapper_spec.rb +21 -0
  87. data/spec/unit/identity_map_spec.rb +126 -0
  88. data/spec/unit/is_spec.rb +80 -0
  89. data/spec/unit/migrator_spec.rb +33 -0
  90. data/spec/unit/model_spec.rb +339 -0
  91. data/spec/unit/naming_conventions_spec.rb +28 -0
  92. data/spec/unit/property_set_spec.rb +96 -0
  93. data/spec/unit/property_spec.rb +447 -0
  94. data/spec/unit/query_spec.rb +485 -0
  95. data/spec/unit/repository_spec.rb +93 -0
  96. data/spec/unit/resource_spec.rb +557 -0
  97. data/spec/unit/scope_spec.rb +131 -0
  98. data/spec/unit/transaction_spec.rb +493 -0
  99. data/spec/unit/type_map_spec.rb +114 -0
  100. data/spec/unit/type_spec.rb +119 -0
  101. metadata +187 -0
@@ -0,0 +1,138 @@
1
+ require File.join(File.dirname(__FILE__), "one_to_many")
2
+ module DataMapper
3
+ module Associations
4
+ module ManyToMany
5
+ extend Assertions
6
+
7
+ # Setup many to many relationship between two models
8
+ # -
9
+ # @api private
10
+ def self.setup(name, model, options = {})
11
+ assert_kind_of 'name', name, Symbol
12
+ assert_kind_of 'model', model, Model
13
+ assert_kind_of 'options', options, Hash
14
+
15
+ repository_name = model.repository.name
16
+
17
+ model.class_eval <<-EOS, __FILE__, __LINE__
18
+ def #{name}(query = {})
19
+ #{name}_association.all(query)
20
+ end
21
+
22
+ def #{name}=(children)
23
+ #{name}_association.replace(children)
24
+ end
25
+
26
+ private
27
+
28
+ def #{name}_association
29
+ @#{name}_association ||= begin
30
+ unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
31
+ raise ArgumentError, 'Relationship #{name.inspect} does not exist'
32
+ end
33
+ association = Proxy.new(relationship, self)
34
+ parent_associations << association
35
+ association
36
+ end
37
+ end
38
+ EOS
39
+
40
+ opts = options.dup
41
+ opts.delete(:through)
42
+ opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
43
+ opts[:parent_model] = model.name
44
+ opts[:repository_name] = repository_name
45
+ opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
46
+ opts[:parent_key] = opts[:parent_key]
47
+ opts[:child_key] = opts[:child_key]
48
+ opts[:mutable] = true
49
+
50
+ names = [ opts[:child_model], opts[:parent_model] ].sort
51
+ model_name = names.join
52
+ storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
53
+
54
+ opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
55
+
56
+ model.has(model.n, opts[:near_relationship_name])
57
+
58
+ relationship = model.relationships(repository_name)[name] = RelationshipChain.new(opts)
59
+
60
+ unless Object.const_defined?(model_name)
61
+ model = DataMapper::Model.new(storage_name)
62
+
63
+ model.class_eval <<-EOS, __FILE__, __LINE__
64
+ def self.name; #{model_name.inspect} end
65
+ def self.default_repository_name; #{repository_name.inspect} end
66
+ def self.many_to_many; true end
67
+ EOS
68
+
69
+ names.each do |n|
70
+ model.belongs_to(Extlib::Inflection.underscore(n).to_sym)
71
+ end
72
+
73
+ Object.const_set(model_name, model)
74
+ end
75
+
76
+ relationship
77
+ end
78
+
79
+ class Proxy < DataMapper::Associations::OneToMany::Proxy
80
+
81
+ def <<(resource)
82
+ resource.save if resource.new_record?
83
+ through = @relationship.child_model.new
84
+ @relationship.child_key.each_with_index do |key, index|
85
+ through.send("#{key.name}=", @relationship.parent_key.key[index].get(@parent))
86
+ end
87
+ remote_relationship.child_key.each_with_index do |key, index|
88
+ through.send("#{key.name}=", remote_relationship.parent_key.key[index].get(resource))
89
+ end
90
+ near_model << through
91
+ super
92
+ end
93
+
94
+ def delete(resource)
95
+ through = near_model.get(*(@parent.key + resource.key))
96
+ near_model.delete(through)
97
+ orphan_resource(super)
98
+ end
99
+
100
+ def clear
101
+ near_model.clear
102
+ super
103
+ end
104
+
105
+ def destroy
106
+ near_model.destroy
107
+ super
108
+ end
109
+
110
+ def save
111
+ end
112
+
113
+ def orphan_resource(resource)
114
+ assert_mutable
115
+ @orphans << resource
116
+ resource
117
+ end
118
+
119
+ def assert_mutable
120
+ end
121
+
122
+ private
123
+
124
+ def remote_relationship
125
+ @remote_relationship ||= @relationship.send(:remote_relationship)
126
+ end
127
+
128
+ def near_model
129
+ @near_model ||= @parent.send(near_relationship_name)
130
+ end
131
+
132
+ def near_relationship_name
133
+ @near_relationship_name ||= @relationship.send(:instance_variable_get, :@near_relationship_name)
134
+ end
135
+ end # class Proxy
136
+ end # module ManyToMany
137
+ end # module Associations
138
+ end # module DataMapper
@@ -0,0 +1,101 @@
1
+ module DataMapper
2
+ module Associations
3
+ module ManyToOne
4
+ extend Assertions
5
+
6
+ # Setup many to one relationship between two models
7
+ # -
8
+ # @api private
9
+ def self.setup(name, model, options = {})
10
+ assert_kind_of 'name', name, Symbol
11
+ assert_kind_of 'model', model, Model
12
+ assert_kind_of 'options', options, Hash
13
+
14
+ repository_name = model.repository.name
15
+
16
+ model.class_eval <<-EOS, __FILE__, __LINE__
17
+ def #{name}
18
+ #{name}_association.nil? ? nil : #{name}_association
19
+ end
20
+
21
+ def #{name}=(parent)
22
+ #{name}_association.replace(parent)
23
+ end
24
+
25
+ private
26
+
27
+ def #{name}_association
28
+ @#{name}_association ||= begin
29
+ unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
30
+ raise ArgumentError, 'Relationship #{name.inspect} does not exist'
31
+ end
32
+ association = Proxy.new(relationship, self)
33
+ child_associations << association
34
+ association
35
+ end
36
+ end
37
+ EOS
38
+
39
+ model.relationships(repository_name)[name] = Relationship.new(
40
+ name,
41
+ repository_name,
42
+ model.name,
43
+ options.fetch(:class_name, Extlib::Inflection.classify(name)),
44
+ options
45
+ )
46
+ end
47
+
48
+ class Proxy
49
+ include Assertions
50
+
51
+ instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not ].include?(m) }
52
+
53
+ def replace(parent)
54
+ @parent = parent
55
+ @relationship.attach_parent(@child, @parent)
56
+ self
57
+ end
58
+
59
+ def save
60
+ return false if @parent.nil?
61
+ return true unless parent.new_record?
62
+
63
+ DataMapper.repository(@relationship.repository_name) do
64
+ parent.save
65
+ end
66
+ end
67
+
68
+ def reload
69
+ @parent = nil
70
+ self
71
+ end
72
+
73
+ def kind_of?(klass)
74
+ super || parent.kind_of?(klass)
75
+ end
76
+
77
+ def respond_to?(method, include_private = false)
78
+ super || parent.respond_to?(method, include_private)
79
+ end
80
+
81
+ private
82
+
83
+ def initialize(relationship, child)
84
+ assert_kind_of 'relationship', relationship, Relationship
85
+ assert_kind_of 'child', child, Resource
86
+
87
+ @relationship = relationship
88
+ @child = child
89
+ end
90
+
91
+ def parent
92
+ @parent ||= @relationship.get_parent(@child)
93
+ end
94
+
95
+ def method_missing(method, *args, &block)
96
+ parent.__send__(method, *args, &block)
97
+ end
98
+ end # class Proxy
99
+ end # module ManyToOne
100
+ end # module Associations
101
+ end # module DataMapper
@@ -0,0 +1,275 @@
1
+ require 'forwardable'
2
+
3
+ module DataMapper
4
+ module Associations
5
+ module OneToMany
6
+ extend Assertions
7
+
8
+ # Setup one to many relationship between two models
9
+ # -
10
+ # @api private
11
+ def self.setup(name, model, options = {})
12
+ assert_kind_of 'name', name, Symbol
13
+ assert_kind_of 'model', model, Model
14
+ assert_kind_of 'options', options, Hash
15
+
16
+ repository_name = model.repository.name
17
+
18
+ model.class_eval <<-EOS, __FILE__, __LINE__
19
+ def #{name}(query = {})
20
+ #{name}_association.all(query)
21
+ end
22
+
23
+ def #{name}=(children)
24
+ #{name}_association.replace(children)
25
+ end
26
+
27
+ private
28
+
29
+ def #{name}_association
30
+ @#{name}_association ||= begin
31
+ unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
32
+ raise ArgumentError, 'Relationship #{name.inspect} does not exist'
33
+ end
34
+ association = Proxy.new(relationship, self)
35
+ parent_associations << association
36
+ association
37
+ end
38
+ end
39
+ EOS
40
+
41
+ model.relationships(repository_name)[name] = if options.has_key?(:through)
42
+ opts = options.dup
43
+
44
+ opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
45
+ opts[:parent_model] = model.name
46
+ opts[:repository_name] = repository_name
47
+ opts[:near_relationship_name] = opts.delete(:through)
48
+ opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
49
+ opts[:parent_key] = opts[:parent_key]
50
+ opts[:child_key] = opts[:child_key]
51
+
52
+ RelationshipChain.new( opts )
53
+ else
54
+ Relationship.new(
55
+ Extlib::Inflection.underscore(Extlib::Inflection.demodulize(model.name)).to_sym,
56
+ repository_name,
57
+ options.fetch(:class_name, Extlib::Inflection.classify(name)),
58
+ model.name,
59
+ options
60
+ )
61
+ end
62
+ end
63
+
64
+ # TODO: look at making this inherit from Collection. The API is
65
+ # almost identical, and it would make more sense for the
66
+ # relationship.get_children method to return a Proxy than a
67
+ # Collection that is wrapped in a Proxy.
68
+ class Proxy
69
+ include Assertions
70
+
71
+ instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not ].include?(m) }
72
+
73
+ # FIXME: remove when RelationshipChain#get_children can return a Collection
74
+ def all(query = {})
75
+ query.empty? ? self : @relationship.get_children(@parent, query)
76
+ end
77
+
78
+ # FIXME: remove when RelationshipChain#get_children can return a Collection
79
+ def first(*args)
80
+ if args.last.respond_to?(:merge)
81
+ query = args.pop
82
+ @relationship.get_children(@parent, query, :first, *args)
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def <<(resource)
89
+ assert_mutable
90
+ super
91
+ relate_resource(resource)
92
+ self
93
+ end
94
+
95
+ def push(*resources)
96
+ assert_mutable
97
+ super
98
+ resources.each { |resource| relate_resource(resource) }
99
+ self
100
+ end
101
+
102
+ def unshift(*resources)
103
+ assert_mutable
104
+ super
105
+ resources.each { |resource| relate_resource(resource) }
106
+ self
107
+ end
108
+
109
+ def replace(other)
110
+ assert_mutable
111
+ each { |resource| orphan_resource(resource) }
112
+ other = other.map { |resource| Hash === resource ? @relationship.child_model.new(resource) : resource }
113
+ super
114
+ other.each { |resource| relate_resource(resource) }
115
+ self
116
+ end
117
+
118
+ def pop
119
+ assert_mutable
120
+ orphan_resource(super)
121
+ end
122
+
123
+ def shift
124
+ assert_mutable
125
+ orphan_resource(super)
126
+ end
127
+
128
+ def delete(resource, &block)
129
+ assert_mutable
130
+ orphan_resource(super)
131
+ end
132
+
133
+ def delete_at(index)
134
+ assert_mutable
135
+ orphan_resource(super)
136
+ end
137
+
138
+ def clear
139
+ assert_mutable
140
+ each { |resource| orphan_resource(resource) }
141
+ super
142
+ self
143
+ end
144
+
145
+ def create(attributes = {})
146
+ assert_mutable
147
+ raise UnsavedParentError, 'You cannot create until the parent is saved' if @parent.new_record?
148
+ super
149
+ end
150
+
151
+ def update(attributes = {})
152
+ assert_mutable
153
+ raise UnsavedParentError, 'You cannot mass-update until the parent is saved' if @parent.new_record?
154
+ super
155
+ end
156
+
157
+ def update!(attributes = {})
158
+ assert_mutable
159
+ raise UnsavedParentError, 'You cannot mass-update without validations until the parent is saved' if @parent.new_record?
160
+ super
161
+ end
162
+
163
+ def destroy
164
+ assert_mutable
165
+ raise UnsavedParentError, 'You cannot mass-delete until the parent is saved' if @parent.new_record?
166
+ super
167
+ end
168
+
169
+ def destroy!
170
+ assert_mutable
171
+ raise UnsavedParentError, 'You cannot mass-delete without validations until the parent is saved' if @parent.new_record?
172
+ super
173
+ end
174
+
175
+ def reload
176
+ @children = nil
177
+ self
178
+ end
179
+
180
+ def save
181
+ assert_mutable
182
+
183
+ # save every resource in the collection
184
+ each { |resource| save_resource(resource) }
185
+
186
+ # save orphan resources
187
+ @orphans.each do |resource|
188
+ begin
189
+ save_resource(resource, nil)
190
+ rescue
191
+ children << resource unless children.frozen? || children.include?(resource)
192
+ raise
193
+ end
194
+ end
195
+
196
+ # FIXME: remove when RelationshipChain#get_children can return a Collection
197
+ # place the children into a Collection if not already
198
+ if children.kind_of?(Array) && !children.frozen?
199
+ @children = @relationship.get_children(@parent).replace(children)
200
+ end
201
+
202
+ true
203
+ end
204
+
205
+ def kind_of?(klass)
206
+ super || children.kind_of?(klass)
207
+ end
208
+
209
+ def respond_to?(method, include_private = false)
210
+ super || children.respond_to?(method, include_private)
211
+ end
212
+
213
+ private
214
+
215
+ def initialize(relationship, parent)
216
+ assert_kind_of 'relationship', relationship, Relationship
217
+ assert_kind_of 'parent', parent, Resource
218
+
219
+ @relationship = relationship
220
+ @parent = parent
221
+ @orphans = []
222
+ end
223
+
224
+ def children
225
+ @children ||= @relationship.get_children(@parent)
226
+ end
227
+
228
+ def assert_mutable
229
+ raise ImmutableAssociationError, 'You can not modify this assocation' if children.frozen?
230
+ end
231
+
232
+ def add_default_association_values(resource)
233
+ return if respond_to?(:default_attributes)
234
+
235
+ @relationship.query.each do |attribute, value|
236
+ next if Query::OPTIONS.include?(attribute) || attribute.kind_of?(Query::Operator) || resource.attribute_loaded?(attribute)
237
+ resource.send("#{attribute}=", value)
238
+ end
239
+ end
240
+
241
+ def relate_resource(resource)
242
+ assert_mutable
243
+ add_default_association_values(resource)
244
+ @orphans.delete(resource)
245
+ resource
246
+ end
247
+
248
+ def orphan_resource(resource)
249
+ assert_mutable
250
+ @orphans << resource
251
+ resource
252
+ end
253
+
254
+ def save_resource(resource, parent = @parent)
255
+ DataMapper.repository(@relationship.repository_name) do
256
+ if parent.nil? && resource.model.respond_to?(:many_to_many)
257
+ resource.destroy
258
+ else
259
+ @relationship.attach_parent(resource, parent)
260
+ resource.save
261
+ end
262
+ end
263
+ end
264
+
265
+ def method_missing(method, *args, &block)
266
+ results = children.__send__(method, *args, &block) if children.respond_to?(method)
267
+
268
+ return self if LazyArray::RETURN_SELF.include?(method) && results.kind_of?(Array)
269
+
270
+ results
271
+ end
272
+ end # class Proxy
273
+ end # module OneToMany
274
+ end # module Associations
275
+ end # module DataMapper