ghost_dm-core 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (254) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +35 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +65 -0
  6. data/LICENSE +20 -0
  7. data/README.md +269 -0
  8. data/Rakefile +4 -0
  9. data/dm-core.gemspec +24 -0
  10. data/lib/dm-core.rb +292 -0
  11. data/lib/dm-core/adapters.rb +222 -0
  12. data/lib/dm-core/adapters/abstract_adapter.rb +237 -0
  13. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  14. data/lib/dm-core/associations/many_to_many.rb +499 -0
  15. data/lib/dm-core/associations/many_to_one.rb +290 -0
  16. data/lib/dm-core/associations/one_to_many.rb +348 -0
  17. data/lib/dm-core/associations/one_to_one.rb +86 -0
  18. data/lib/dm-core/associations/relationship.rb +663 -0
  19. data/lib/dm-core/backwards.rb +13 -0
  20. data/lib/dm-core/collection.rb +1515 -0
  21. data/lib/dm-core/core_ext/kernel.rb +23 -0
  22. data/lib/dm-core/core_ext/pathname.rb +6 -0
  23. data/lib/dm-core/core_ext/symbol.rb +10 -0
  24. data/lib/dm-core/identity_map.rb +7 -0
  25. data/lib/dm-core/model.rb +874 -0
  26. data/lib/dm-core/model/hook.rb +103 -0
  27. data/lib/dm-core/model/is.rb +32 -0
  28. data/lib/dm-core/model/property.rb +249 -0
  29. data/lib/dm-core/model/relationship.rb +378 -0
  30. data/lib/dm-core/model/scope.rb +89 -0
  31. data/lib/dm-core/property.rb +866 -0
  32. data/lib/dm-core/property/binary.rb +21 -0
  33. data/lib/dm-core/property/boolean.rb +20 -0
  34. data/lib/dm-core/property/class.rb +17 -0
  35. data/lib/dm-core/property/date.rb +10 -0
  36. data/lib/dm-core/property/date_time.rb +10 -0
  37. data/lib/dm-core/property/decimal.rb +36 -0
  38. data/lib/dm-core/property/discriminator.rb +44 -0
  39. data/lib/dm-core/property/float.rb +16 -0
  40. data/lib/dm-core/property/integer.rb +22 -0
  41. data/lib/dm-core/property/invalid_value_error.rb +22 -0
  42. data/lib/dm-core/property/lookup.rb +27 -0
  43. data/lib/dm-core/property/numeric.rb +38 -0
  44. data/lib/dm-core/property/object.rb +34 -0
  45. data/lib/dm-core/property/serial.rb +14 -0
  46. data/lib/dm-core/property/string.rb +38 -0
  47. data/lib/dm-core/property/text.rb +9 -0
  48. data/lib/dm-core/property/time.rb +10 -0
  49. data/lib/dm-core/property_set.rb +177 -0
  50. data/lib/dm-core/query.rb +1366 -0
  51. data/lib/dm-core/query/conditions/comparison.rb +911 -0
  52. data/lib/dm-core/query/conditions/operation.rb +721 -0
  53. data/lib/dm-core/query/direction.rb +36 -0
  54. data/lib/dm-core/query/operator.rb +35 -0
  55. data/lib/dm-core/query/path.rb +114 -0
  56. data/lib/dm-core/query/sort.rb +39 -0
  57. data/lib/dm-core/relationship_set.rb +72 -0
  58. data/lib/dm-core/repository.rb +226 -0
  59. data/lib/dm-core/resource.rb +1214 -0
  60. data/lib/dm-core/resource/persistence_state.rb +75 -0
  61. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  62. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  63. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  64. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  65. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  66. data/lib/dm-core/resource/persistence_state/transient.rb +80 -0
  67. data/lib/dm-core/spec/lib/adapter_helpers.rb +64 -0
  68. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  69. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  70. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  71. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  72. data/lib/dm-core/spec/setup.rb +174 -0
  73. data/lib/dm-core/spec/shared/adapter_spec.rb +341 -0
  74. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  75. data/lib/dm-core/spec/shared/resource_spec.rb +1232 -0
  76. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  77. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +176 -0
  78. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  79. data/lib/dm-core/support/assertions.rb +8 -0
  80. data/lib/dm-core/support/chainable.rb +18 -0
  81. data/lib/dm-core/support/deprecate.rb +12 -0
  82. data/lib/dm-core/support/descendant_set.rb +89 -0
  83. data/lib/dm-core/support/equalizer.rb +48 -0
  84. data/lib/dm-core/support/ext/array.rb +22 -0
  85. data/lib/dm-core/support/ext/blank.rb +25 -0
  86. data/lib/dm-core/support/ext/hash.rb +67 -0
  87. data/lib/dm-core/support/ext/module.rb +47 -0
  88. data/lib/dm-core/support/ext/object.rb +57 -0
  89. data/lib/dm-core/support/ext/string.rb +24 -0
  90. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  91. data/lib/dm-core/support/hook.rb +405 -0
  92. data/lib/dm-core/support/inflections.rb +60 -0
  93. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  94. data/lib/dm-core/support/inflector/methods.rb +151 -0
  95. data/lib/dm-core/support/lazy_array.rb +451 -0
  96. data/lib/dm-core/support/local_object_space.rb +13 -0
  97. data/lib/dm-core/support/logger.rb +201 -0
  98. data/lib/dm-core/support/mash.rb +176 -0
  99. data/lib/dm-core/support/naming_conventions.rb +90 -0
  100. data/lib/dm-core/support/ordered_set.rb +380 -0
  101. data/lib/dm-core/support/subject.rb +33 -0
  102. data/lib/dm-core/support/subject_set.rb +250 -0
  103. data/lib/dm-core/version.rb +3 -0
  104. data/script/performance.rb +275 -0
  105. data/script/profile.rb +218 -0
  106. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  107. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  108. data/spec/public/associations/many_to_many_spec.rb +197 -0
  109. data/spec/public/associations/many_to_one_spec.rb +83 -0
  110. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  111. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  112. data/spec/public/associations/one_to_many_spec.rb +81 -0
  113. data/spec/public/associations/one_to_one_spec.rb +176 -0
  114. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  115. data/spec/public/collection_spec.rb +69 -0
  116. data/spec/public/finalize_spec.rb +76 -0
  117. data/spec/public/model/hook_spec.rb +246 -0
  118. data/spec/public/model/property_spec.rb +88 -0
  119. data/spec/public/model/relationship_spec.rb +1040 -0
  120. data/spec/public/model_spec.rb +462 -0
  121. data/spec/public/property/binary_spec.rb +41 -0
  122. data/spec/public/property/boolean_spec.rb +22 -0
  123. data/spec/public/property/class_spec.rb +28 -0
  124. data/spec/public/property/date_spec.rb +22 -0
  125. data/spec/public/property/date_time_spec.rb +22 -0
  126. data/spec/public/property/decimal_spec.rb +23 -0
  127. data/spec/public/property/discriminator_spec.rb +135 -0
  128. data/spec/public/property/float_spec.rb +22 -0
  129. data/spec/public/property/integer_spec.rb +22 -0
  130. data/spec/public/property/object_spec.rb +107 -0
  131. data/spec/public/property/serial_spec.rb +22 -0
  132. data/spec/public/property/string_spec.rb +22 -0
  133. data/spec/public/property/text_spec.rb +63 -0
  134. data/spec/public/property/time_spec.rb +22 -0
  135. data/spec/public/property_spec.rb +341 -0
  136. data/spec/public/resource_spec.rb +288 -0
  137. data/spec/public/sel_spec.rb +53 -0
  138. data/spec/public/setup_spec.rb +145 -0
  139. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  140. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  141. data/spec/public/shared/collection_shared_spec.rb +1667 -0
  142. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  143. data/spec/rcov.opts +6 -0
  144. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  145. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  146. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  147. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  148. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  149. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  150. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  151. data/spec/semipublic/associations_spec.rb +177 -0
  152. data/spec/semipublic/collection_spec.rb +110 -0
  153. data/spec/semipublic/model_spec.rb +96 -0
  154. data/spec/semipublic/property/binary_spec.rb +13 -0
  155. data/spec/semipublic/property/boolean_spec.rb +47 -0
  156. data/spec/semipublic/property/class_spec.rb +33 -0
  157. data/spec/semipublic/property/date_spec.rb +43 -0
  158. data/spec/semipublic/property/date_time_spec.rb +46 -0
  159. data/spec/semipublic/property/decimal_spec.rb +83 -0
  160. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  161. data/spec/semipublic/property/float_spec.rb +82 -0
  162. data/spec/semipublic/property/integer_spec.rb +82 -0
  163. data/spec/semipublic/property/lookup_spec.rb +29 -0
  164. data/spec/semipublic/property/serial_spec.rb +13 -0
  165. data/spec/semipublic/property/string_spec.rb +13 -0
  166. data/spec/semipublic/property/text_spec.rb +31 -0
  167. data/spec/semipublic/property/time_spec.rb +50 -0
  168. data/spec/semipublic/property_spec.rb +114 -0
  169. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  170. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  171. data/spec/semipublic/query/path_spec.rb +471 -0
  172. data/spec/semipublic/query_spec.rb +3682 -0
  173. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  174. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  175. data/spec/semipublic/resource/state/dirty_spec.rb +162 -0
  176. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  177. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  178. data/spec/semipublic/resource/state_spec.rb +230 -0
  179. data/spec/semipublic/resource_spec.rb +23 -0
  180. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  181. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  182. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  183. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  184. data/spec/spec.opts +5 -0
  185. data/spec/spec_helper.rb +38 -0
  186. data/spec/support/core_ext/hash.rb +10 -0
  187. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  188. data/spec/support/properties/huge_integer.rb +17 -0
  189. data/spec/unit/array_spec.rb +23 -0
  190. data/spec/unit/blank_spec.rb +73 -0
  191. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  192. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  193. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  194. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  195. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  196. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  197. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  198. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  199. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  200. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  201. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  202. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  203. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  204. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  205. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  206. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  207. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  208. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  216. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  217. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  218. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  219. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  220. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  221. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  222. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  223. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  224. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  226. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  227. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  228. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  229. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  230. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  231. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  232. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  233. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  237. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  239. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  240. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  241. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  242. data/spec/unit/hash_spec.rb +28 -0
  243. data/spec/unit/hook_spec.rb +1235 -0
  244. data/spec/unit/inflections_spec.rb +16 -0
  245. data/spec/unit/lazy_array_spec.rb +1949 -0
  246. data/spec/unit/mash_spec.rb +312 -0
  247. data/spec/unit/module_spec.rb +71 -0
  248. data/spec/unit/object_spec.rb +38 -0
  249. data/spec/unit/try_dup_spec.rb +46 -0
  250. data/tasks/ci.rake +1 -0
  251. data/tasks/spec.rake +38 -0
  252. data/tasks/yard.rake +9 -0
  253. data/tasks/yardstick.rake +19 -0
  254. metadata +365 -0
@@ -0,0 +1,89 @@
1
+ module DataMapper
2
+ module Model
3
+ # Module with query scoping functionality.
4
+ #
5
+ # Scopes are implemented using simple array based
6
+ # stack that is thread local. Default scope can be set
7
+ # on a per repository basis.
8
+ #
9
+ # Scopes are merged as new queries are nested.
10
+ # It is also possible to get exclusive scope access
11
+ # using +with_exclusive_scope+
12
+ module Scope
13
+ # @api private
14
+ def default_scope(repository_name = default_repository_name)
15
+ @default_scope ||= {}
16
+
17
+ default_repository_name = self.default_repository_name
18
+
19
+ @default_scope[repository_name] ||= if repository_name == default_repository_name
20
+ {}
21
+ else
22
+ default_scope(default_repository_name).dup
23
+ end
24
+ end
25
+
26
+ # Returns query on top of scope stack
27
+ #
28
+ # @api private
29
+ def query
30
+ repository.new_query(self, current_scope).freeze
31
+ end
32
+
33
+ # @api private
34
+ def current_scope
35
+ scope_stack.last || default_scope(repository.name)
36
+ end
37
+
38
+ protected
39
+
40
+ # Pushes given query on top of the stack
41
+ #
42
+ # @param [Hash, Query] Query to add to current scope nesting
43
+ #
44
+ # @api private
45
+ def with_scope(query)
46
+ options = if query.kind_of?(Hash)
47
+ query
48
+ else
49
+ query.options
50
+ end
51
+
52
+ # merge the current scope with the passed in query
53
+ with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) }
54
+ end
55
+
56
+ # Pushes given query on top of scope stack and yields
57
+ # given block, then pops the stack. During block execution
58
+ # queries previously pushed onto the stack
59
+ # have no effect.
60
+ #
61
+ # @api private
62
+ def with_exclusive_scope(query)
63
+ query = if query.kind_of?(Hash)
64
+ repository.new_query(self, query)
65
+ else
66
+ query.dup
67
+ end
68
+
69
+ scope_stack = self.scope_stack
70
+ scope_stack << query.options
71
+
72
+ begin
73
+ yield query.freeze
74
+ ensure
75
+ scope_stack.pop
76
+ end
77
+ end
78
+
79
+ # Initializes (if necessary) and returns current scope stack
80
+ # @api private
81
+ def scope_stack
82
+ scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
83
+ scope_stack_for[object_id] ||= []
84
+ end
85
+ end # module Scope
86
+
87
+ include Scope
88
+ end # module Model
89
+ end # module DataMapper
@@ -0,0 +1,866 @@
1
+ module DataMapper
2
+ # = Properties
3
+ # Properties for a model are not derived from a database structure, but
4
+ # instead explicitly declared inside your model class definitions. These
5
+ # properties then map (or, if using automigrate, generate) fields in your
6
+ # repository/database.
7
+ #
8
+ # If you are coming to DataMapper from another ORM framework, such as
9
+ # ActiveRecord, this may be a fundamental difference in thinking to you.
10
+ # However, there are several advantages to defining your properties in your
11
+ # models:
12
+ #
13
+ # * information about your model is centralized in one place: rather than
14
+ # having to dig out migrations, xml or other configuration files.
15
+ # * use of mixins can be applied to model properties: better code reuse
16
+ # * having information centralized in your models, encourages you and the
17
+ # developers on your team to take a model-centric view of development.
18
+ # * it provides the ability to use Ruby's access control functions.
19
+ # * and, because DataMapper only cares about properties explicitly defined
20
+ # in your models, DataMapper plays well with legacy databases, and shares
21
+ # databases easily with other applications.
22
+ #
23
+ # == Declaring Properties
24
+ # Inside your class, you call the property method for each property you want
25
+ # to add. The only two required arguments are the name and type, everything
26
+ # else is optional.
27
+ #
28
+ # class Post
29
+ # include DataMapper::Resource
30
+ #
31
+ # property :title, String, :required => true # Cannot be null
32
+ # property :publish, Boolean, :default => false # Default value for new records is false
33
+ # end
34
+ #
35
+ # By default, DataMapper supports the following primitive (Ruby) types
36
+ # also called core properties:
37
+ #
38
+ # * Boolean
39
+ # * Class (datastore primitive is the same as String. Used for Inheritance)
40
+ # * Date
41
+ # * DateTime
42
+ # * Decimal
43
+ # * Float
44
+ # * Integer
45
+ # * Object (marshalled out during serialization)
46
+ # * String (default length is 50)
47
+ # * Text (limit of 65k characters by default)
48
+ # * Time
49
+ #
50
+ # == Limiting Access
51
+ # Property access control is uses the same terminology Ruby does. Properties
52
+ # are public by default, but can also be declared private or protected as
53
+ # needed (via the :accessor option).
54
+ #
55
+ # class Post
56
+ # include DataMapper::Resource
57
+ #
58
+ # property :title, String, :accessor => :private # Both reader and writer are private
59
+ # property :body, Text, :accessor => :protected # Both reader and writer are protected
60
+ # end
61
+ #
62
+ # Access control is also analogous to Ruby attribute readers and writers, and can
63
+ # be declared using :reader and :writer, in addition to :accessor.
64
+ #
65
+ # class Post
66
+ # include DataMapper::Resource
67
+ #
68
+ # property :title, String, :writer => :private # Only writer is private
69
+ # property :tags, String, :reader => :protected # Only reader is protected
70
+ # end
71
+ #
72
+ # == Overriding Accessors
73
+ # The reader/writer for any property can be overridden in the same manner that Ruby
74
+ # attr readers/writers can be. After the property is defined, just add your custom
75
+ # reader or writer:
76
+ #
77
+ # class Post
78
+ # include DataMapper::Resource
79
+ #
80
+ # property :title, String
81
+ #
82
+ # def title=(new_title)
83
+ # raise ArgumentError if new_title != 'Lee is l337'
84
+ # super(new_title)
85
+ # end
86
+ # end
87
+ #
88
+ # Calling super ensures that any validators defined for the property are kept active.
89
+ #
90
+ # == Lazy Loading
91
+ # By default, some properties are not loaded when an object is fetched in
92
+ # DataMapper. These lazily loaded properties are fetched on demand when their
93
+ # accessor is called for the first time (as it is often unnecessary to
94
+ # instantiate -every- property -every- time an object is loaded). For
95
+ # instance, DataMapper::Property::Text fields are lazy loading by default,
96
+ # although you can over-ride this behavior if you wish:
97
+ #
98
+ # Example:
99
+ #
100
+ # class Post
101
+ # include DataMapper::Resource
102
+ #
103
+ # property :title, String # Loads normally
104
+ # property :body, Text # Is lazily loaded by default
105
+ # end
106
+ #
107
+ # If you want to over-ride the lazy loading on any field you can set it to a
108
+ # context or false to disable it with the :lazy option. Contexts allow
109
+ # multiple lazy properties to be loaded at one time. If you set :lazy to
110
+ # true, it is placed in the :default context
111
+ #
112
+ # class Post
113
+ # include DataMapper::Resource
114
+ #
115
+ # property :title, String # Loads normally
116
+ # property :body, Text, :lazy => false # The default is now over-ridden
117
+ # property :comment, String, :lazy => [ :detailed ] # Loads in the :detailed context
118
+ # property :author, String, :lazy => [ :summary, :detailed ] # Loads in :summary & :detailed context
119
+ # end
120
+ #
121
+ # Delaying the request for lazy-loaded attributes even applies to objects
122
+ # accessed through associations. In a sense, DataMapper anticipates that
123
+ # you will likely be iterating over objects in associations and rolls all
124
+ # of the load commands for lazy-loaded properties into one request from
125
+ # the database.
126
+ #
127
+ # Example:
128
+ #
129
+ # Widget.get(1).components
130
+ # # loads when the post object is pulled from database, by default
131
+ #
132
+ # Widget.get(1).components.first.body
133
+ # # loads the values for the body property on all objects in the
134
+ # # association, rather than just this one.
135
+ #
136
+ # Widget.get(1).components.first.comment
137
+ # # loads both comment and author for all objects in the association
138
+ # # since they are both in the :detailed context
139
+ #
140
+ # == Keys
141
+ # Properties can be declared as primary or natural keys on a table.
142
+ # You should a property as the primary key of the table:
143
+ #
144
+ # Examples:
145
+ #
146
+ # property :id, Serial # auto-incrementing key
147
+ # property :legacy_pk, String, :key => true # 'natural' key
148
+ #
149
+ # This is roughly equivalent to ActiveRecord's <tt>set_primary_key</tt>,
150
+ # though non-integer data types may be used, thus DataMapper supports natural
151
+ # keys. When a property is declared as a natural key, accessing the object
152
+ # using the indexer syntax <tt>Class[key]</tt> remains valid.
153
+ #
154
+ # User.get(1)
155
+ # # when :id is the primary key on the users table
156
+ # User.get('bill')
157
+ # # when :name is the primary (natural) key on the users table
158
+ #
159
+ # == Indices
160
+ # You can add indices for your properties by using the <tt>:index</tt>
161
+ # option. If you use <tt>true</tt> as the option value, the index will be
162
+ # automatically named. If you want to name the index yourself, use a symbol
163
+ # as the value.
164
+ #
165
+ # property :last_name, String, :index => true
166
+ # property :first_name, String, :index => :name
167
+ #
168
+ # You can create multi-column composite indices by using the same symbol in
169
+ # all the columns belonging to the index. The columns will appear in the
170
+ # index in the order they are declared.
171
+ #
172
+ # property :last_name, String, :index => :name
173
+ # property :first_name, String, :index => :name
174
+ # # => index on (last_name, first_name)
175
+ #
176
+ # If you want to make the indices unique, use <tt>:unique_index</tt> instead
177
+ # of <tt>:index</tt>
178
+ #
179
+ # == Inferred Validations
180
+ # If you require the dm-validations plugin, auto-validations will
181
+ # automatically be mixed-in in to your model classes: validation rules that
182
+ # are inferred when properties are declared with specific column restrictions.
183
+ #
184
+ # class Post
185
+ # include DataMapper::Resource
186
+ #
187
+ # property :title, String, :length => 250, :min => 0, :max => 250
188
+ # # => infers 'validates_length :title'
189
+ #
190
+ # property :title, String, :required => true
191
+ # # => infers 'validates_present :title'
192
+ #
193
+ # property :email, String, :format => :email_address
194
+ # # => infers 'validates_format :email, :with => :email_address'
195
+ #
196
+ # property :title, String, :length => 255, :required => true
197
+ # # => infers both 'validates_length' as well as 'validates_present'
198
+ # # better: property :title, String, :length => 1..255
199
+ # end
200
+ #
201
+ # This functionality is available with the dm-validations gem. For more information
202
+ # about validations, check the documentation for dm-validations.
203
+ #
204
+ # == Default Values
205
+ # To set a default for a property, use the <tt>:default</tt> key. The
206
+ # property will be set to the value associated with that key the first time
207
+ # it is accessed, or when the resource is saved if it hasn't been set with
208
+ # another value already. This value can be a static value, such as 'hello'
209
+ # but it can also be a proc that will be evaluated when the property is read
210
+ # before its value has been set. The property is set to the return of the
211
+ # proc. The proc is passed two values, the resource the property is being set
212
+ # for and the property itself.
213
+ #
214
+ # property :display_name, String, :default => lambda { |resource, property| resource.login }
215
+ #
216
+ # Word of warning. Don't try to read the value of the property you're setting
217
+ # the default for in the proc. An infinite loop will ensue.
218
+ #
219
+ # == Embedded Values (not implemented yet)
220
+ # As an alternative to extraneous has_one relationships, consider using an
221
+ # EmbeddedValue.
222
+ #
223
+ # == Property options reference
224
+ #
225
+ # :accessor if false, neither reader nor writer methods are
226
+ # created for this property
227
+ #
228
+ # :reader if false, reader method is not created for this property
229
+ #
230
+ # :writer if false, writer method is not created for this property
231
+ #
232
+ # :lazy if true, property value is only loaded when on first read
233
+ # if false, property value is always loaded
234
+ # if a symbol, property value is loaded with other properties
235
+ # in the same group
236
+ #
237
+ # :default default value of this property
238
+ #
239
+ # :allow_nil if true, property may have a nil value on save
240
+ #
241
+ # :key name of the key associated with this property.
242
+ #
243
+ # :field field in the data-store which the property corresponds to
244
+ #
245
+ # :length string field length
246
+ #
247
+ # :format format for autovalidation. Use with dm-validations plugin.
248
+ #
249
+ # :index if true, index is created for the property. If a Symbol, index
250
+ # is named after Symbol value instead of being based on property name.
251
+ #
252
+ # :unique_index true specifies that index on this property should be unique
253
+ #
254
+ # :auto_validation if true, automatic validation is performed on the property
255
+ #
256
+ # :validates validation context. Use together with dm-validations.
257
+ #
258
+ # :unique if true, property column is unique. Properties of type Serial
259
+ # are unique by default.
260
+ #
261
+ # :precision Indicates the number of significant digits. Usually only makes sense
262
+ # for float type properties. Must be >= scale option value. Default is 10.
263
+ #
264
+ # :scale The number of significant digits to the right of the decimal point.
265
+ # Only makes sense for float type properties. Must be > 0.
266
+ # Default is nil for Float type and 10 for BigDecimal
267
+ #
268
+ # == Overriding default Property options
269
+ #
270
+ # There is the ability to reconfigure a Property and it's subclasses by explicitly
271
+ # setting a value in the Property, eg:
272
+ #
273
+ # # set all String properties to have a default length of 255
274
+ # DataMapper::Property::String.length(255)
275
+ #
276
+ # # set all Boolean properties to not allow nil (force true or false)
277
+ # DataMapper::Property::Boolean.allow_nil(false)
278
+ #
279
+ # # set all properties to be required by default
280
+ # DataMapper::Property.required(true)
281
+ #
282
+ # # turn off auto-validation for all properties by default
283
+ # DataMapper::Property.auto_validation(false)
284
+ #
285
+ # # set all mutator methods to be private by default
286
+ # DataMapper::Property.writer(:private)
287
+ #
288
+ # Please note that this has no effect when a subclass has explicitly
289
+ # defined it's own option. For example, setting the String length to
290
+ # 255 will not affect the Text property even though it inherits from
291
+ # String, because it sets it's own default length to 65535.
292
+ #
293
+ # == Misc. Notes
294
+ # * Properties declared as strings will default to a length of 50, rather than
295
+ # 255 (typical max varchar column size). To overload the default, pass
296
+ # <tt>:length => 255</tt> or <tt>:length => 0..255</tt>. Since DataMapper
297
+ # does not introspect for properties, this means that legacy database tables
298
+ # may need their <tt>String</tt> columns defined with a <tt>:length</tt> so
299
+ # that DM does not apply an un-needed length validation, or allow overflow.
300
+ # * You may declare a Property with the data-type of <tt>Class</tt>.
301
+ # see SingleTableInheritance for more on how to use <tt>Class</tt> columns.
302
+ class Property
303
+ include DataMapper::Assertions
304
+ include Subject
305
+ extend Equalizer
306
+
307
+ equalize :model, :name, :options
308
+
309
+ PRIMITIVES = [
310
+ TrueClass,
311
+ ::String,
312
+ ::Float,
313
+ ::Integer,
314
+ ::BigDecimal,
315
+ ::DateTime,
316
+ ::Date,
317
+ ::Time,
318
+ ::Class
319
+ ].to_set.freeze
320
+
321
+ OPTIONS = [
322
+ :load_as, :dump_as, :coercion_method,
323
+ :accessor, :reader, :writer,
324
+ :lazy, :default, :key, :field,
325
+ :index, :unique_index,
326
+ :unique, :allow_nil, :allow_blank, :required
327
+ ]
328
+
329
+ # Possible :visibility option values
330
+ VISIBILITY_OPTIONS = [ :public, :protected, :private ].to_set.freeze
331
+
332
+ # Invalid property names
333
+ INVALID_NAMES = (Resource.instance_methods +
334
+ Resource.private_instance_methods +
335
+ Query::OPTIONS.to_a
336
+ ).map { |name| name.to_s }
337
+
338
+ attr_reader :load_as, :dump_as, :coercion_method,
339
+ :model, :name, :instance_variable_name,
340
+ :reader_visibility, :writer_visibility, :options,
341
+ :default, :repository_name, :allow_nil, :allow_blank, :required
342
+
343
+ alias_method :load_class, :load_as
344
+ alias_method :dump_class, :dump_as
345
+
346
+ class << self
347
+ extend Deprecate
348
+
349
+ deprecate :all_descendants, :descendants
350
+
351
+ # @api semipublic
352
+ def determine_class(type)
353
+ return type if type < DataMapper::Property::Object
354
+ find_class(DataMapper::Inflector.demodulize(type.name))
355
+ end
356
+
357
+ # @api private
358
+ def demodulized_names
359
+ @demodulized_names ||= {}
360
+ end
361
+
362
+ # @api semipublic
363
+ def find_class(name)
364
+ klass = demodulized_names[name]
365
+ klass ||= const_get(name) if const_defined?(name)
366
+ klass
367
+ end
368
+
369
+ # @api public
370
+ def descendants
371
+ @descendants ||= DescendantSet.new
372
+ end
373
+
374
+ # @api private
375
+ def inherited(descendant)
376
+ # Descendants is a tree rooted in DataMapper::Property that tracks
377
+ # inheritance. We pre-calculate each comparison value (demodulized
378
+ # class name) to achieve a Hash[]-time lookup, rather than walk the
379
+ # entire descendant tree and calculate names on-demand (expensive,
380
+ # redundant).
381
+ #
382
+ # Since the algorithm relegates property class name lookups to a flat
383
+ # namespace, we need to ensure properties defined outside of DM don't
384
+ # override built-ins (Serial, String, etc) by merely defining a property
385
+ # of a same name. We avoid this by only ever adding to the lookup
386
+ # table. Given that DM loads its own property classes first, we can
387
+ # assume that their names are "reserved" when added to the table.
388
+ #
389
+ # External property authors who want to provide "replacements" for
390
+ # builtins (e.g. in a non-DM-supported adapter) should follow the
391
+ # convention of wrapping those properties in a module, and include'ing
392
+ # the module on the model class directly. This bypasses the DM-hooked
393
+ # const_missing lookup that would normally check this table.
394
+ descendants << descendant
395
+
396
+ Property.demodulized_names[DataMapper::Inflector.demodulize(descendant.name)] ||= descendant
397
+
398
+ # inherit accepted options
399
+ descendant.accepted_options.concat(accepted_options)
400
+
401
+ # inherit the option values
402
+ options.each { |key, value| descendant.send(key, value) }
403
+
404
+ super
405
+ end
406
+
407
+ # @api public
408
+ def accepted_options
409
+ @accepted_options ||= []
410
+ end
411
+
412
+ # @api public
413
+ def accept_options(*args)
414
+ accepted_options.concat(args)
415
+
416
+ # create methods for each new option
417
+ args.each do |property_option|
418
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
419
+ def self.#{property_option}(value = Undefined) # def self.unique(value = Undefined)
420
+ return @#{property_option} if value.equal?(Undefined) # return @unique if value.equal?(Undefined)
421
+ descendants.each do |descendant| # descendants.each do |descendant|
422
+ unless descendant.instance_variable_defined?(:@#{property_option}) # unless descendant.instance_variable_defined?(:@unique)
423
+ descendant.#{property_option}(value) # descendant.unique(value)
424
+ end # end
425
+ end # end
426
+ @#{property_option} = value # @unique = value
427
+ end # end
428
+ RUBY
429
+ end
430
+
431
+ descendants.each { |descendant| descendant.accepted_options.concat(args) }
432
+ end
433
+
434
+ # @api private
435
+ def nullable(*args)
436
+ # :required is preferable to :allow_nil, but :nullable maps precisely to :allow_nil
437
+ raise "#nullable is deprecated, use #required instead (#{caller.first})"
438
+ end
439
+
440
+ # Gives all the options set on this property
441
+ #
442
+ # @return [Hash] with all options and their values set on this property
443
+ #
444
+ # @api public
445
+ def options
446
+ options = {}
447
+ accepted_options.each do |name|
448
+ options[name] = send(name) if instance_variable_defined?("@#{name}")
449
+ end
450
+ options
451
+ end
452
+
453
+ # @api deprecated
454
+ def primitive(*args)
455
+ warn "DataMapper::Property.primitive is deprecated, use .load_as instead (#{caller.first})"
456
+ load_as(*args)
457
+ end
458
+ end
459
+
460
+ accept_options *Property::OPTIONS
461
+
462
+ # A hook to allow properties to extend or modify the model it's bound to.
463
+ # Implementations are not supposed to modify the state of the property
464
+ # class, and should produce no side-effects on the property instance.
465
+ def bind
466
+ # no op
467
+ end
468
+
469
+ # Supplies the field in the data-store which the property corresponds to
470
+ #
471
+ # @return [String] name of field in data-store
472
+ #
473
+ # @api semipublic
474
+ def field(repository_name = nil)
475
+ if repository_name
476
+ raise "Passing in +repository_name+ to #{self.class}#field is deprecated (#{caller.first})"
477
+ end
478
+
479
+ # defer setting the field with the adapter specific naming
480
+ # conventions until after the adapter has been setup
481
+ @field ||= model.field_naming_convention(self.repository_name).call(self).freeze
482
+ end
483
+
484
+ # Returns true if property is unique. Serial properties and keys
485
+ # are unique by default.
486
+ #
487
+ # @return [Boolean]
488
+ # true if property has uniq index defined, false otherwise
489
+ #
490
+ # @api public
491
+ def unique?
492
+ !!@unique
493
+ end
494
+
495
+ # Returns index name if property has index.
496
+ #
497
+ # @return [Boolean, Symbol, Array]
498
+ # returns true if property is indexed by itself
499
+ # returns a Symbol if the property is indexed with other properties
500
+ # returns an Array if the property belongs to multiple indexes
501
+ # returns false if the property does not belong to any indexes
502
+ #
503
+ # @api public
504
+ attr_reader :index
505
+
506
+ # Returns true if property has unique index. Serial properties and
507
+ # keys are unique by default.
508
+ #
509
+ # @return [Boolean, Symbol, Array]
510
+ # returns true if property is indexed by itself
511
+ # returns a Symbol if the property is indexed with other properties
512
+ # returns an Array if the property belongs to multiple indexes
513
+ # returns false if the property does not belong to any indexes
514
+ #
515
+ # @api public
516
+ attr_reader :unique_index
517
+
518
+ # Returns whether or not the property is to be lazy-loaded
519
+ #
520
+ # @return [Boolean]
521
+ # true if the property is to be lazy-loaded
522
+ #
523
+ # @api public
524
+ def lazy?
525
+ @lazy
526
+ end
527
+
528
+ # Returns whether or not the property is a key or a part of a key
529
+ #
530
+ # @return [Boolean]
531
+ # true if the property is a key or a part of a key
532
+ #
533
+ # @api public
534
+ def key?
535
+ @key
536
+ end
537
+
538
+ # Returns whether or not the property is "serial" (auto-incrementing)
539
+ #
540
+ # @return [Boolean]
541
+ # whether or not the property is "serial"
542
+ #
543
+ # @api public
544
+ def serial?
545
+ @serial
546
+ end
547
+
548
+ # Returns whether or not the property must be non-nil and non-blank
549
+ #
550
+ # @return [Boolean]
551
+ # whether or not the property is required
552
+ #
553
+ # @api public
554
+ def required?
555
+ @required
556
+ end
557
+
558
+ # Returns whether or not the property can accept 'nil' as it's value
559
+ #
560
+ # @return [Boolean]
561
+ # whether or not the property can accept 'nil'
562
+ #
563
+ # @api public
564
+ def allow_nil?
565
+ @allow_nil
566
+ end
567
+
568
+ # Returns whether or not the property can be a blank value
569
+ #
570
+ # @return [Boolean]
571
+ # whether or not the property can be blank
572
+ #
573
+ # @api public
574
+ def allow_blank?
575
+ @allow_blank
576
+ end
577
+
578
+ # Standardized reader method for the property
579
+ #
580
+ # @param [Resource] resource
581
+ # model instance for which this property is to be loaded
582
+ #
583
+ # @return [Object]
584
+ # the value of this property for the provided instance
585
+ #
586
+ # @raise [ArgumentError] "+resource+ should be a Resource, but was ...."
587
+ #
588
+ # @api private
589
+ def get(resource)
590
+ get!(resource)
591
+ end
592
+
593
+ # Fetch the ivar value in the resource
594
+ #
595
+ # @param [Resource] resource
596
+ # model instance for which this property is to be unsafely loaded
597
+ #
598
+ # @return [Object]
599
+ # current @ivar value of this property in +resource+
600
+ #
601
+ # @api private
602
+ def get!(resource)
603
+ resource.instance_variable_get(instance_variable_name)
604
+ end
605
+
606
+ # Provides a standardized setter method for the property
607
+ #
608
+ # @param [Resource] resource
609
+ # the resource to get the value from
610
+ # @param [Object] value
611
+ # the value to set in the resource
612
+ #
613
+ # @return [Object]
614
+ # +value+ after being typecasted according to this property's primitive
615
+ #
616
+ # @raise [ArgumentError] "+resource+ should be a Resource, but was ...."
617
+ #
618
+ # @api private
619
+ def set(resource, value)
620
+ set!(resource, typecast(value))
621
+ end
622
+
623
+ # Set the ivar value in the resource
624
+ #
625
+ # @param [Resource] resource
626
+ # the resource to set
627
+ # @param [Object] value
628
+ # the value to set in the resource
629
+ #
630
+ # @return [Object]
631
+ # the value set in the resource
632
+ #
633
+ # @api private
634
+ def set!(resource, value)
635
+ resource.instance_variable_set(instance_variable_name, value)
636
+ end
637
+
638
+ # Check if the attribute corresponding to the property is loaded
639
+ #
640
+ # @param [Resource] resource
641
+ # model instance for which the attribute is to be tested
642
+ #
643
+ # @return [Boolean]
644
+ # true if the attribute is loaded in the resource
645
+ #
646
+ # @api private
647
+ def loaded?(resource)
648
+ resource.instance_variable_defined?(instance_variable_name)
649
+ end
650
+
651
+ # Loads lazy columns when get or set is called.
652
+ #
653
+ # @param [Resource] resource
654
+ # model instance for which lazy loaded attribute are loaded
655
+ #
656
+ # @api private
657
+ def lazy_load(resource)
658
+ return if loaded?(resource)
659
+ resource.__send__(:lazy_load, lazy_load_properties)
660
+ end
661
+
662
+ # @api private
663
+ def lazy_load_properties
664
+ @lazy_load_properties ||=
665
+ begin
666
+ properties = self.properties
667
+ properties.in_context(lazy? ? [ self ] : properties.defaults)
668
+ end
669
+ end
670
+
671
+ # @api private
672
+ def properties
673
+ @properties ||= model.properties(repository_name)
674
+ end
675
+
676
+ # @api semipublic
677
+ def typecast(value)
678
+ Virtus::Coercion[value.class].send(coercion_method, value)
679
+ end
680
+
681
+ # Test the value to see if it is a valid value for this Property
682
+ #
683
+ # @param [Object] loaded_value
684
+ # the value to be tested
685
+ #
686
+ # @return [Boolean]
687
+ # true if the value is valid
688
+ #
689
+ # @api semipulic
690
+ def valid?(value, negated = false)
691
+ dumped_value = dump(value)
692
+
693
+ if required? && dumped_value.nil?
694
+ negated || false
695
+ else
696
+ value_dumped?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
697
+ end
698
+ end
699
+
700
+ # Asserts value is valid
701
+ #
702
+ # @param [Object] loaded_value
703
+ # the value to be tested
704
+ #
705
+ # @return [Boolean]
706
+ # true if the value is valid
707
+ #
708
+ # @raise [Property::InvalidValueError]
709
+ # if value is not valid
710
+ def assert_valid_value(value)
711
+ unless valid?(value)
712
+ raise Property::InvalidValueError.new(self,value)
713
+ end
714
+ true
715
+ end
716
+
717
+ # Returns a concise string representation of the property instance.
718
+ #
719
+ # @return [String]
720
+ # Concise string representation of the property instance.
721
+ #
722
+ # @api public
723
+ def inspect
724
+ "#<#{self.class.name} @model=#{model.inspect} @name=#{name.inspect}>"
725
+ end
726
+
727
+ # Test a value to see if it matches the primitive type
728
+ #
729
+ # @param [Object] value
730
+ # value to test
731
+ #
732
+ # @return [Boolean]
733
+ # true if the value is the correct type
734
+ #
735
+ # @api semipublic
736
+ def primitive?(value)
737
+ warn "#primitive? is deprecated, use #value_dumped? instead (#{caller.first})"
738
+ value_dumped?(value)
739
+ end
740
+
741
+ def primitive
742
+ warn "#primitive is deprecated, use #dump_as instead (#{caller.first})"
743
+ dump_as
744
+ end
745
+
746
+ # @api semipublic
747
+ def value_dumped?(value)
748
+ value.kind_of?(dump_as)
749
+ end
750
+
751
+ # @api semipublic
752
+ def value_loaded?(value)
753
+ value.kind_of?(load_as)
754
+ end
755
+
756
+ protected
757
+
758
+ # @api semipublic
759
+ def initialize(model, name, options = {})
760
+ options = options.to_hash.dup
761
+
762
+ if INVALID_NAMES.include?(name.to_s) || (kind_of?(Boolean) && INVALID_NAMES.include?("#{name}?"))
763
+ raise ArgumentError,
764
+ "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method or a query option"
765
+ end
766
+
767
+ assert_valid_options(options)
768
+
769
+ predefined_options = self.class.options
770
+
771
+ @repository_name = model.repository_name
772
+ @model = model
773
+ @name = name.to_s.chomp('?').to_sym
774
+ @options = predefined_options.merge(options).freeze
775
+ @instance_variable_name = "@#{@name}".freeze
776
+ @coercion_method = @options.fetch(:coercion_method)
777
+
778
+ @load_as = self.class.load_as
779
+ @dump_as = self.class.dump_as
780
+
781
+ @field = @options[:field].freeze unless @options[:field].nil?
782
+ @default = @options[:default]
783
+
784
+ @serial = @options.fetch(:serial, false)
785
+ @key = @options.fetch(:key, @serial)
786
+ @unique = @options.fetch(:unique, @key ? :key : false)
787
+ @required = @options.fetch(:required, @key)
788
+ @allow_nil = @options.fetch(:allow_nil, !@required)
789
+ @allow_blank = @options.fetch(:allow_blank, !@required)
790
+ @index = @options.fetch(:index, false)
791
+ @unique_index = @options.fetch(:unique_index, @unique)
792
+ @lazy = @options.fetch(:lazy, false) && !@key
793
+
794
+ determine_visibility
795
+
796
+ bind
797
+ end
798
+
799
+ # @api private
800
+ def assert_valid_options(options)
801
+ keys = options.keys
802
+
803
+ if (unknown_keys = keys - self.class.accepted_options).any?
804
+ raise ArgumentError, "options #{unknown_keys.map { |key| key.inspect }.join(' and ')} are unknown"
805
+ end
806
+
807
+ options.each do |key, value|
808
+ boolean_value = value == true || value == false
809
+
810
+ case key
811
+ when :field
812
+ assert_kind_of "options[:#{key}]", value, ::String
813
+
814
+ when :default
815
+ if value.nil?
816
+ raise ArgumentError, "options[:#{key}] must not be nil"
817
+ end
818
+
819
+ when :serial, :key, :allow_nil, :allow_blank, :required, :auto_validation
820
+ unless boolean_value
821
+ raise ArgumentError, "options[:#{key}] must be either true or false"
822
+ end
823
+
824
+ if key == :required && (keys.include?(:allow_nil) || keys.include?(:allow_blank))
825
+ raise ArgumentError, 'options[:required] cannot be mixed with :allow_nil or :allow_blank'
826
+ end
827
+
828
+ when :index, :unique_index, :unique, :lazy
829
+ unless boolean_value || value.kind_of?(Symbol) || (value.kind_of?(Array) && value.any? && value.all? { |val| val.kind_of?(Symbol) })
830
+ raise ArgumentError, "options[:#{key}] must be either true, false, a Symbol or an Array of Symbols"
831
+ end
832
+
833
+ when :length
834
+ assert_kind_of "options[:#{key}]", value, Range, ::Integer
835
+
836
+ when :size, :precision, :scale
837
+ assert_kind_of "options[:#{key}]", value, ::Integer
838
+
839
+ when :reader, :writer, :accessor
840
+ assert_kind_of "options[:#{key}]", value, Symbol
841
+
842
+ unless VISIBILITY_OPTIONS.include?(value)
843
+ raise ArgumentError, "options[:#{key}] must be #{VISIBILITY_OPTIONS.join(' or ')}"
844
+ end
845
+ end
846
+ end
847
+ end
848
+
849
+ # Assert given visibility value is supported.
850
+ #
851
+ # Will raise ArgumentError if this Property's reader and writer
852
+ # visibilities are not included in VISIBILITY_OPTIONS.
853
+ #
854
+ # @return [undefined]
855
+ #
856
+ # @raise [ArgumentError] "property visibility must be :public, :protected, or :private"
857
+ #
858
+ # @api private
859
+ def determine_visibility
860
+ default_accessor = @options.fetch(:accessor, :public)
861
+
862
+ @reader_visibility = @options.fetch(:reader, default_accessor)
863
+ @writer_visibility = @options.fetch(:writer, default_accessor)
864
+ end
865
+ end # class Property
866
+ end