ghost_dm-core 1.3.0.beta

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 (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