sam-dm-core 0.9.6

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 (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,58 @@
1
+ module DataMapper
2
+ module Scope
3
+ Model.append_extensions self
4
+
5
+ # @api private
6
+ def default_scope(repository_name = nil)
7
+ repository_name = self.default_repository_name if repository_name == :default || repository_name.nil?
8
+ @default_scope ||= Hash.new{|h,k| h[k] = {}}
9
+ @default_scope[repository_name]
10
+ end
11
+
12
+ # @api private
13
+ def query
14
+ scope_stack.last
15
+ end
16
+
17
+ protected
18
+
19
+ # @api semipublic
20
+ def with_scope(query, &block)
21
+ # merge the current scope with the passed in query
22
+ with_exclusive_scope(self.query ? self.query.merge(query) : query, &block)
23
+ end
24
+
25
+ # @api semipublic
26
+ def with_exclusive_scope(query, &block)
27
+ query = DataMapper::Query.new(repository, self, query) if query.kind_of?(Hash)
28
+
29
+ scope_stack << query
30
+
31
+ begin
32
+ return yield(query)
33
+ ensure
34
+ scope_stack.pop
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # @api private
41
+ def merge_with_default_scope(query)
42
+ DataMapper::Query.new(query.repository, query.model, default_scope_for_query(query)).update(query)
43
+ end
44
+
45
+ # @api private
46
+ def scope_stack
47
+ scope_stack_for = Thread.current[:dm_scope_stack] ||= Hash.new { |h,model| h[model] = [] }
48
+ scope_stack_for[self]
49
+ end
50
+
51
+ # @api private
52
+ def default_scope_for_query(query)
53
+ repository_name = query.repository.name
54
+ default_repository_name = query.model.default_repository_name
55
+ self.default_scope(default_repository_name).merge(self.default_scope(repository_name))
56
+ end
57
+ end # module Scope
58
+ end # module DataMapper
@@ -0,0 +1,7 @@
1
+ dir = Pathname(__FILE__).dirname.expand_path / 'support'
2
+
3
+ require dir / 'array'
4
+ require dir / 'assertions'
5
+ require dir / 'errors'
6
+ require dir / 'kernel'
7
+ require dir / 'symbol'
@@ -0,0 +1,13 @@
1
+ class Array
2
+
3
+ ##
4
+ # atm it assumes self is an array of [key,value]-arrays
5
+ # this is just a better way to make hashes than Hash[*array.flatten]
6
+ # since you cannot flatten only one level in ruby 1.8.6
7
+ #
8
+ def to_hash
9
+ h = {}
10
+ self.each{ |k,v| h[k] = v }
11
+ h
12
+ end
13
+ end # class Symbol
@@ -0,0 +1,8 @@
1
+ module DataMapper
2
+ module Assertions
3
+ def assert_kind_of(name, value, *klasses)
4
+ klasses.each { |k| return if value.kind_of?(k) }
5
+ raise ArgumentError, "+#{name}+ should be #{klasses.map { |k| k.name } * ' or '}, but was #{value.class.name}", caller(2)
6
+ end
7
+ end
8
+ end # module DataMapper
@@ -0,0 +1,23 @@
1
+ #Some useful errors types
2
+ module DataMapper
3
+ class ValidationError < StandardError; end
4
+
5
+ class ObjectNotFoundError < StandardError; end
6
+
7
+ class MaterializationError < StandardError; end
8
+
9
+ class RepositoryNotSetupError < StandardError; end
10
+
11
+ class IncompleteResourceError < StandardError; end
12
+
13
+ class PersistenceError < StandardError; end
14
+
15
+ class PluginNotFoundError < StandardError; end
16
+ end # module DataMapper
17
+
18
+ class StandardError
19
+ # Displays the specific error message and the backtrace associated with it.
20
+ def display
21
+ "#{message}\n\t#{backtrace.join("\n\t")}"
22
+ end
23
+ end # class StandardError
@@ -0,0 +1,7 @@
1
+ module Kernel
2
+ # Delegates to DataMapper::repository.
3
+ # Will not overwrite if a method of the same name is pre-defined.
4
+ def repository(*args, &block)
5
+ DataMapper.repository(*args, &block)
6
+ end
7
+ end # module Kernel
@@ -0,0 +1,41 @@
1
+ class Symbol
2
+ def gt
3
+ DataMapper::Query::Operator.new(self, :gt)
4
+ end
5
+
6
+ def gte
7
+ DataMapper::Query::Operator.new(self, :gte)
8
+ end
9
+
10
+ def lt
11
+ DataMapper::Query::Operator.new(self, :lt)
12
+ end
13
+
14
+ def lte
15
+ DataMapper::Query::Operator.new(self, :lte)
16
+ end
17
+
18
+ def not
19
+ DataMapper::Query::Operator.new(self, :not)
20
+ end
21
+
22
+ def eql
23
+ DataMapper::Query::Operator.new(self, :eql)
24
+ end
25
+
26
+ def like
27
+ DataMapper::Query::Operator.new(self, :like)
28
+ end
29
+
30
+ def in
31
+ DataMapper::Query::Operator.new(self, :in)
32
+ end
33
+
34
+ def asc
35
+ DataMapper::Query::Operator.new(self, :asc)
36
+ end
37
+
38
+ def desc
39
+ DataMapper::Query::Operator.new(self, :desc)
40
+ end
41
+ end # class Symbol
@@ -0,0 +1,267 @@
1
+ # TODO: move to dm-more/dm-transactions
2
+
3
+ module DataMapper
4
+ class Transaction
5
+
6
+ attr_reader :transaction_primitives, :adapters, :state
7
+
8
+ #
9
+ # Create a new DataMapper::Transaction
10
+ #
11
+ # @see DataMapper::Transaction#link
12
+ #
13
+ # In fact, it just calls #link with the given arguments at the end of the
14
+ # constructor.
15
+ #
16
+ def initialize(*things, &block)
17
+ @transaction_primitives = {}
18
+ @state = :none
19
+ @adapters = {}
20
+ link(*things)
21
+ commit(&block) if block_given?
22
+ end
23
+
24
+ #
25
+ # Associate this Transaction with some things.
26
+ #
27
+ # @param things<any number of Object> the things you want this Transaction
28
+ # associated with
29
+ # @details [things a Transaction may be associatied with]
30
+ # DataMapper::Adapters::AbstractAdapter subclasses will be added as
31
+ # adapters as is.
32
+ # Arrays will have their elements added.
33
+ # DataMapper::Repositories will have their @adapters added.
34
+ # DataMapper::Resource subclasses will have all the repositories of all
35
+ # their properties added.
36
+ # DataMapper::Resource instances will have all repositories of all their
37
+ # properties added.
38
+ # @param block<Block> a block (taking one argument, the Transaction) to execute
39
+ # within this transaction. The transaction will begin and commit around
40
+ # the block, and rollback if an exception is raised.
41
+ #
42
+ def link(*things, &block)
43
+ raise "Illegal state for link: #{@state}" unless @state == :none
44
+ things.each do |thing|
45
+ if thing.is_a?(Array)
46
+ link(*thing)
47
+ elsif thing.is_a?(DataMapper::Adapters::AbstractAdapter)
48
+ @adapters[thing] = :none
49
+ elsif thing.is_a?(DataMapper::Repository)
50
+ link(thing.adapter)
51
+ elsif thing.is_a?(Class) && thing.ancestors.include?(DataMapper::Resource)
52
+ link(*thing.repositories)
53
+ elsif thing.is_a?(DataMapper::Resource)
54
+ link(thing.model)
55
+ else
56
+ raise "Unknown argument to #{self}#link: #{thing.inspect}"
57
+ end
58
+ end
59
+ return commit(&block) if block_given?
60
+ return self
61
+ end
62
+
63
+ #
64
+ # Begin the transaction
65
+ #
66
+ # Before #begin is called, the transaction is not valid and can not be used.
67
+ #
68
+ def begin
69
+ raise "Illegal state for begin: #{@state}" unless @state == :none
70
+ each_adapter(:connect_adapter, [:log_fatal_transaction_breakage])
71
+ each_adapter(:begin_adapter, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
72
+ @state = :begin
73
+ end
74
+
75
+ #
76
+ # Commit the transaction
77
+ #
78
+ # @param block<Block> a block (taking the one argument, the Transaction) to
79
+ # execute within this transaction. The transaction will begin and commit
80
+ # around the block, and roll back if an exception is raised.
81
+ #
82
+ # @note
83
+ # If no block is given, it will simply commit any changes made since the
84
+ # Transaction did #begin.
85
+ #
86
+ def commit(&block)
87
+ if block_given?
88
+ raise "Illegal state for commit with block: #{@state}" unless @state == :none
89
+ begin
90
+ self.begin
91
+ rval = within(&block)
92
+ self.commit if @state == :begin
93
+ return rval
94
+ rescue Exception => e
95
+ self.rollback if @state == :begin
96
+ raise e
97
+ end
98
+ else
99
+ raise "Illegal state for commit without block: #{@state}" unless @state == :begin
100
+ each_adapter(:prepare_adapter, [:rollback_and_close_adapter_if_begin, :rollback_prepared_and_close_adapter_if_prepare])
101
+ each_adapter(:commit_adapter, [:log_fatal_transaction_breakage])
102
+ each_adapter(:close_adapter, [:log_fatal_transaction_breakage])
103
+ @state = :commit
104
+ end
105
+ end
106
+
107
+ #
108
+ # Rollback the transaction
109
+ #
110
+ # Will undo all changes made during the transaction.
111
+ #
112
+ def rollback
113
+ raise "Illegal state for rollback: #{@state}" unless @state == :begin
114
+ each_adapter(:rollback_adapter_if_begin, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
115
+ each_adapter(:rollback_prepared_adapter_if_prepare, [:rollback_prepared_and_close_adapter_if_begin, :close_adapter_if_none])
116
+ each_adapter(:close_adapter_if_open, [:log_fatal_transaction_breakage])
117
+ @state = :rollback
118
+ end
119
+
120
+ #
121
+ # Execute a block within this Transaction.
122
+ #
123
+ # @param block<Block> the block of code to execute.
124
+ #
125
+ # @note
126
+ # No #begin, #commit or #rollback is performed in #within, but this
127
+ # Transaction will pushed on the per thread stack of transactions for each
128
+ # adapter it is associated with, and it will ensures that it will pop the
129
+ # Transaction away again after the block is finished.
130
+ #
131
+ def within(&block)
132
+ raise "No block provided" unless block_given?
133
+ raise "Illegal state for within: #{@state}" unless @state == :begin
134
+ @adapters.each do |adapter, state|
135
+ adapter.push_transaction(self)
136
+ end
137
+ begin
138
+ return yield(self)
139
+ ensure
140
+ @adapters.each do |adapter, state|
141
+ adapter.pop_transaction
142
+ end
143
+ end
144
+ end
145
+
146
+ def method_missing(meth, *args, &block)
147
+ if args.size == 1 && args.first.is_a?(DataMapper::Adapters::AbstractAdapter)
148
+ if (match = meth.to_s.match(/^(.*)_if_(none|begin|prepare|rollback|commit)$/))
149
+ if self.respond_to?(match[1], true)
150
+ self.send(match[1], args.first) if state_for(args.first).to_s == match[2]
151
+ else
152
+ super
153
+ end
154
+ elsif (match = meth.to_s.match(/^(.*)_unless_(none|begin|prepare|rollback|commit)$/))
155
+ if self.respond_to?(match[1], true)
156
+ self.send(match[1], args.first) unless state_for(args.first).to_s == match[2]
157
+ else
158
+ super
159
+ end
160
+ else
161
+ super
162
+ end
163
+ else
164
+ super
165
+ end
166
+ end
167
+
168
+ def primitive_for(adapter)
169
+ raise "Unknown adapter #{adapter}" unless @adapters.include?(adapter)
170
+ raise "No primitive for #{adapter}" unless @transaction_primitives.include?(adapter)
171
+ @transaction_primitives[adapter]
172
+ end
173
+
174
+ private
175
+
176
+ def validate_primitive(primitive)
177
+ [:close, :begin, :prepare, :rollback, :rollback_prepared, :commit].each do |meth|
178
+ raise "Invalid primitive #{primitive}: doesnt respond_to?(#{meth.inspect})" unless primitive.respond_to?(meth)
179
+ end
180
+ return primitive
181
+ end
182
+
183
+ def each_adapter(method, on_fail)
184
+ begin
185
+ @adapters.each do |adapter, state|
186
+ self.send(method, adapter)
187
+ end
188
+ rescue Exception => e
189
+ @adapters.each do |adapter, state|
190
+ on_fail.each do |fail_handler|
191
+ begin
192
+ self.send(fail_handler, adapter)
193
+ rescue Exception => e2
194
+ DataMapper.logger.fatal("#{self}#each_adapter(#{method.inspect}, #{on_fail.inspect}) failed with #{e.inspect}: #{e.backtrace.join("\n")} - and when sending #{fail_handler} to #{adapter} we failed again with #{e2.inspect}: #{e2.backtrace.join("\n")}")
195
+ end
196
+ end
197
+ end
198
+ raise e
199
+ end
200
+ end
201
+
202
+ def state_for(adapter)
203
+ raise "Unknown adapter #{adapter}" unless @adapters.include?(adapter)
204
+ @adapters[adapter]
205
+ end
206
+
207
+ def do_adapter(adapter, what, prerequisite)
208
+ raise "No primitive for #{adapter}" unless @transaction_primitives.include?(adapter)
209
+ raise "Illegal state for #{what}: #{state_for(adapter)}" unless state_for(adapter) == prerequisite
210
+ DataMapper.logger.debug("#{adapter.name}: #{what}")
211
+ @transaction_primitives[adapter].send(what)
212
+ @adapters[adapter] = what
213
+ end
214
+
215
+ def log_fatal_transaction_breakage(adapter)
216
+ DataMapper.logger.fatal("#{self} experienced a totally broken transaction execution. Presenting member #{adapter.inspect}.")
217
+ end
218
+
219
+ def connect_adapter(adapter)
220
+ raise "Already a primitive for adapter #{adapter}" unless @transaction_primitives[adapter].nil?
221
+ @transaction_primitives[adapter] = validate_primitive(adapter.transaction_primitive)
222
+ end
223
+
224
+ def close_adapter_if_open(adapter)
225
+ if @transaction_primitives.include?(adapter)
226
+ close_adapter(adapter)
227
+ end
228
+ end
229
+
230
+ def close_adapter(adapter)
231
+ raise "No primitive for adapter" unless @transaction_primitives.include?(adapter)
232
+ @transaction_primitives[adapter].close
233
+ @transaction_primitives.delete(adapter)
234
+ end
235
+
236
+ def begin_adapter(adapter)
237
+ do_adapter(adapter, :begin, :none)
238
+ end
239
+
240
+ def prepare_adapter(adapter)
241
+ do_adapter(adapter, :prepare, :begin);
242
+ end
243
+
244
+ def commit_adapter(adapter)
245
+ do_adapter(adapter, :commit, :prepare)
246
+ end
247
+
248
+ def rollback_adapter(adapter)
249
+ do_adapter(adapter, :rollback, :begin)
250
+ end
251
+
252
+ def rollback_prepared_adapter(adapter)
253
+ do_adapter(adapter, :rollback_prepared, :prepare)
254
+ end
255
+
256
+ def rollback_prepared_and_close_adapter(adapter)
257
+ rollback_prepared_adapter(adapter)
258
+ close_adapter(adapter)
259
+ end
260
+
261
+ def rollback_and_close_adapter(adapter)
262
+ rollback_adapter(adapter)
263
+ close_adapter(adapter)
264
+ end
265
+
266
+ end # class Transaction
267
+ end # module DataMapper
@@ -0,0 +1,160 @@
1
+ module DataMapper
2
+
3
+ # = Types
4
+ # Provides means of writing custom types for properties. Each type is based
5
+ # on a ruby primitive and handles its own serialization and materialization,
6
+ # and therefore is responsible for providing those methods.
7
+ #
8
+ # To see complete list of supported types, see documentation for
9
+ # DataMapper::Property::TYPES
10
+ #
11
+ # == Defining new Types
12
+ # To define a new type, subclass DataMapper::Type, pick ruby primitive, and
13
+ # set the options for this type.
14
+ #
15
+ # class MyType < DataMapper::Type
16
+ # primitive String
17
+ # size 10
18
+ # end
19
+ #
20
+ # Following this, you will be able to use MyType as a type for any given
21
+ # property. If special materialization and serialization is required,
22
+ # override the class methods
23
+ #
24
+ # class MyType < DataMapper::Type
25
+ # primitive String
26
+ # size 10
27
+ #
28
+ # def self.dump(value, property)
29
+ # <work some magic>
30
+ # end
31
+ #
32
+ # def self.load(value)
33
+ # <work some magic>
34
+ # end
35
+ # end
36
+ class Type
37
+ PROPERTY_OPTIONS = [
38
+ :accessor, :reader, :writer,
39
+ :lazy, :default, :nullable, :key, :serial, :field, :size, :length,
40
+ :format, :index, :unique_index, :check, :ordinal, :auto_validation,
41
+ :validates, :unique, :track, :precision, :scale
42
+ ]
43
+
44
+ PROPERTY_OPTION_ALIASES = {
45
+ :size => [ :length ]
46
+ }
47
+
48
+ class << self
49
+
50
+ def configure(primitive_type, options)
51
+ @_primitive_type = primitive_type
52
+ @_options = options
53
+
54
+ def self.inherited(base)
55
+ base.primitive @_primitive_type
56
+
57
+ @_options.each do |k, v|
58
+ base.send(k, v)
59
+ end
60
+ end
61
+
62
+ self
63
+ end
64
+
65
+ # The Ruby primitive type to use as basis for this type. See
66
+ # DataMapper::Property::TYPES for list of types.
67
+ #
68
+ # @param primitive<Class, nil>
69
+ # The class for the primitive. If nil is passed in, it returns the
70
+ # current primitive
71
+ #
72
+ # @return <Class> if the <primitive> param is nil, return the current primitive.
73
+ #
74
+ # @api public
75
+ def primitive(primitive = nil)
76
+ return @primitive if primitive.nil?
77
+
78
+ # TODO: change Integer to be used internally once most in-the-wild code
79
+ # is updated to use Integer for properties instead of Fixnum, or before
80
+ # DM 1.0, whichever comes first
81
+ if Fixnum == primitive
82
+ warn "#{primitive} properties are deprecated. Please use Integer instead"
83
+ primitive = Integer
84
+ end
85
+
86
+ @primitive = primitive
87
+ end
88
+
89
+ # Load DataMapper::Property options
90
+ PROPERTY_OPTIONS.each do |property_option|
91
+ self.class_eval <<-EOS, __FILE__, __LINE__
92
+ def #{property_option}(arg = nil)
93
+ return @#{property_option} if arg.nil?
94
+
95
+ @#{property_option} = arg
96
+ end
97
+ EOS
98
+ end
99
+
100
+ # Create property aliases
101
+ PROPERTY_OPTION_ALIASES.each do |property_option, aliases|
102
+ aliases.each do |ali|
103
+ self.class_eval <<-EOS, __FILE__, __LINE__
104
+ alias #{ali} #{property_option}
105
+ EOS
106
+ end
107
+ end
108
+
109
+ # Gives all the options set on this type
110
+ #
111
+ # @return <Hash> with all options and their values set on this type
112
+ #
113
+ # @api public
114
+ def options
115
+ options = {}
116
+ PROPERTY_OPTIONS.each do |method|
117
+ next if (value = send(method)).nil?
118
+ options[method] = value
119
+ end
120
+ options
121
+ end
122
+ end
123
+
124
+ # Stub instance method for dumping
125
+ #
126
+ # @param value<Object, nil> the value to dump
127
+ # @param property<Property, nil> the property the type is being used by
128
+ #
129
+ # @return <Object> Dumped object
130
+ #
131
+ # @api public
132
+ def self.dump(value, property)
133
+ value
134
+ end
135
+
136
+ # Stub instance method for loading
137
+ #
138
+ # @param value<Object, nil> the value to serialize
139
+ # @param property<Property, nil> the property the type is being used by
140
+ #
141
+ # @return <Object> Serialized object. Must be the same type as the Ruby primitive
142
+ #
143
+ # @api public
144
+ def self.load(value, property)
145
+ value
146
+ end
147
+
148
+ def self.bind(property)
149
+ # This method should not modify the state of this type class, and
150
+ # should produce no side-effects on the type class. It's just a
151
+ # hook to allow your custom-type to modify the property it's bound to.
152
+ end
153
+
154
+ end # class Type
155
+
156
+ def self.Type(primitive_type, options = {})
157
+ Class.new(Type).configure(primitive_type, options)
158
+ end
159
+
160
+ end # module DataMapper