sbf-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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
data/README.md ADDED
@@ -0,0 +1,269 @@
1
+ Why DataMapper?
2
+ ===============
3
+
4
+ Open Development
5
+ ----------------
6
+
7
+ DataMapper sports a very accessible code-base and a welcoming community.
8
+ Outside contributions and feedback are welcome and encouraged, especially
9
+ constructive criticism. Make your voice heard! [Submit an issue](https://github.com/firespring/dm-core/issues),
10
+ speak up on our [mailing-list](http://groups.google.com/group/datamapper/),
11
+ chat with us on [irc](irc://irc.freenode.net/#datamapper), write a spec, get it
12
+ reviewed, ask for commit rights. It's as easy as that to become a contributor.
13
+
14
+ Identity Map
15
+ ------------
16
+
17
+ One row in the data-store should equal one object reference. Pretty simple idea.
18
+ Pretty profound impact. If you run the following code in ActiveRecord you'll
19
+ see all `false` results. Do the same in DataMapper and it's
20
+ `true` all the way down.
21
+
22
+ ``` ruby
23
+ repository do
24
+ @parent = Tree.first(:name => 'bob')
25
+
26
+ @parent.children.each do |child|
27
+ puts @parent.equal?(child.parent) # => true
28
+ end
29
+ end
30
+ ```
31
+
32
+ This makes DataMapper faster and allocate less resources to get things done.
33
+
34
+ Dirty Tracking
35
+ --------------
36
+
37
+ When you save a model back to your data-store, DataMapper will only write
38
+ the fields that actually changed. So it plays well with others. You can
39
+ use it in an Integration data-store without worrying that your application will
40
+ be a bad actor causing trouble for all of your other processes.
41
+
42
+ Eager Loading
43
+ -------------
44
+
45
+ Ready for something amazing? The following example executes only two queries
46
+ regardless of how many rows the inner and outer queries return.
47
+
48
+ ``` ruby
49
+ repository do
50
+ Zoo.all.each { |zoo| zoo.exhibits.to_a }
51
+ end
52
+ ```
53
+
54
+ Pretty impressive huh? The idea is that you aren't going to load a set of
55
+ objects and use only an association in just one of them. This should hold up
56
+ pretty well against a 99% rule. When you don't want it to work like this, just
57
+ load the item you want in it's own set. So the DataMapper thinks ahead. We
58
+ like to call it "performant by default". This feature single-handedly wipes
59
+ out the "N+1 Query Problem". No need to specify an `:include` option in
60
+ your finders.
61
+
62
+ Laziness Can Be A Virtue
63
+ ------------------------
64
+
65
+ Text fields are expensive in data-stores. They're generally stored in a
66
+ different place than the rest of your data. So instead of a fast sequential
67
+ read from your hard-drive, your data-store server has to hop around all over the
68
+ place to get what it needs. Since ActiveRecord returns everything by default,
69
+ adding a text field to a table slows everything down drastically, across the
70
+ board.
71
+
72
+ Not so with the DataMapper. Text fields are lazily loaded, meaning they
73
+ only load when you need them. If you want more control you can enable or
74
+ disable this feature for any field (not just text-fields) by passing a
75
+ `:lazy` option to your field mapping with a value of `true` or
76
+ `false`.
77
+
78
+ ``` ruby
79
+ class Animal
80
+ include DataMapper::Resource
81
+
82
+ property :name, String
83
+ property :description, Text, :lazy => false
84
+ end
85
+ ```
86
+
87
+ Plus, lazy-loading of Text fields happens automatically and intelligently when
88
+ working with associations. The following only issues 2 queries to load up all
89
+ of the notes fields on each animal:
90
+
91
+ ``` ruby
92
+ repository do
93
+ Animal.all.each { |animal| animal.description.to_a }
94
+ end
95
+ ```
96
+
97
+ Did you notice the `#to_a` call in the above example? That
98
+ was necessary because even DataMapper collections are lazy. If you don't
99
+ iterate over them, or in this case ask them to become Arrays, they won't
100
+ execute until you need them. We needed to call `#to_a` to force
101
+ the lazy load because without it, the above example would have only
102
+ executed one query. This extra bit of laziness can come in very handy,
103
+ for example:
104
+
105
+ ``` ruby
106
+ animals = Animal.all
107
+ description = 'foo'
108
+
109
+ animals.each do |animal|
110
+ animal.update(:description => description)
111
+ end
112
+ ```
113
+
114
+ In the above example, the Animals won't be retrieved until you actually
115
+ need them. This comes in handy in cases where you initialize the
116
+ collection before you know if you need it, like in a web app controller.
117
+
118
+ Collection Chaining
119
+ -------------------
120
+
121
+ DataMapper's lazy collections are also handy because you can get the
122
+ same effect as named scopes, without any special syntax, eg:
123
+
124
+ ``` ruby
125
+ class Animal
126
+ # ... setup ...
127
+
128
+ def self.mammals
129
+ all(:mammal => true)
130
+ end
131
+
132
+ def self.zoo(zoo)
133
+ all(:zoo => zoo)
134
+ end
135
+ end
136
+
137
+ zoo = Zoo.first(:name => 'Greater Vancouver Zoo')
138
+
139
+ Animal.mammals.zoo(zoo).to_a # => executes one query
140
+ ```
141
+
142
+ In the above example, we ask the Animal model for all the mammals,
143
+ and then all the animals in a specific zoo, and DataMapper will chain
144
+ the collection queries together and execute a single query to retrieve
145
+ the matching records. There's no special syntax, and no custom DSLs
146
+ to learn, it's just plain ruby all the way down.
147
+
148
+ You can even use this on association collections, eg:
149
+
150
+ ``` ruby
151
+ zoo.animals.mammals.to_a # => executes one query
152
+ ```
153
+
154
+ Custom Properties
155
+ -----------------
156
+
157
+ With DataMapper it is possible to create custom properties for your models.
158
+ Consider this example:
159
+
160
+ ``` ruby
161
+ module DataMapper
162
+ class Property
163
+ class Email < String
164
+ required true
165
+ format /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
166
+ end
167
+ end
168
+ end
169
+
170
+ class User
171
+ include DataMapper::Resource
172
+
173
+ property :id, Serial
174
+ property :email, Email
175
+ end
176
+ ```
177
+
178
+ This way there won't be a need to repeat same property options every time you
179
+ add an email to a model. In the example above we create an Email property which
180
+ is just a String with additional pre-configured options: `required` and
181
+ `format`. Please note that it is possible to override these options when
182
+ declaring a property, like this:
183
+
184
+ ``` ruby
185
+ class Member
186
+ include DataMapper::Resource
187
+
188
+ property :id, Serial
189
+ property :email, Email, :required => false
190
+ end
191
+ ```
192
+
193
+ Plays Well With Others
194
+ ----------------------
195
+
196
+ In ActiveRecord, all your fields are mapped, whether you want them or not.
197
+ This slows things down. In the DataMapper you define your mappings in your
198
+ model. So instead of an _ALTER TABLE ADD field_ in your data-store, you simply
199
+ add a `property :name, String` to your model. DRY. No schema.rb. No
200
+ migration files to conflict or die without reverting changes. Your model
201
+ drives the data-store, not the other way around.
202
+
203
+ Unless of course you want to map to a legacy data-store. Raise your hand if you
204
+ like seeing a method called `col2Name` on your model just because
205
+ that's what it's called in an old data-store you can't afford to change right
206
+ now? In DataMapper you control the mappings:
207
+
208
+ ``` ruby
209
+ class Fruit
210
+ include DataMapper::Resource
211
+
212
+ storage_names[:repo] = 'frt'
213
+
214
+ property :name, String, :field => 'col2Name'
215
+ end
216
+ ```
217
+
218
+ All Ruby, All The Time
219
+ ----------------------
220
+
221
+ It's great that ActiveRecord allows you to write SQL when you need to, but
222
+ should we have to so often?
223
+
224
+ DataMapper supports issuing your own query, but it also provides more helpers
225
+ and a unique hash-based condition syntax to cover more of the use-cases where
226
+ issuing your own SQL would have been the only way to go. For example, any
227
+ finder option that's non-standard is considered a condition. So you can write
228
+ `Zoo.all(:name => 'Dallas')` and DataMapper will look for zoos with the
229
+ name of 'Dallas'.
230
+
231
+ It's just a little thing, but it's so much nicer than writing
232
+ `Zoo.find(:all, :conditions => ['name = ?', 'Dallas'])`. What if you
233
+ need other comparisons though? Try these:
234
+
235
+ ``` ruby
236
+ # 'gt' means greater-than. We also do 'lt'.
237
+ Person.all(:age.gt => 30)
238
+
239
+ # 'gte' means greather-than-or-equal-to. We also do 'lte'.
240
+ Person.all(:age.gte => 30)
241
+
242
+ # 'not' allows you to match all people without the name "bob"
243
+ Person.all(:name.not => 'bob')
244
+
245
+ # If the value of a pair is an Array, we do an IN-clause for you.
246
+ Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ])
247
+
248
+ # Does a NOT IN () clause for you.
249
+ Person.all(:name.not => [ 'bob', 'rick', 'steve' ])
250
+ ```
251
+
252
+ See? Fewer SQL fragments dirtying your Ruby code. And that's just a few of the
253
+ nice syntax tweaks DataMapper delivers out of the box...
254
+
255
+ Note on Patches/Pull Requests
256
+ -----------------------------
257
+
258
+ * Fork the project.
259
+ * Make your feature addition or bug fix.
260
+ * Add tests for it. This is important so I don't break it in a
261
+ future version unintentionally.
262
+ * Commit, do not mess with rakefile, version, or history.
263
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
264
+ * Send me a pull request. Bonus points for topic branches.
265
+
266
+ Copyright
267
+ ---------
268
+
269
+ Copyright (c) 2012 Dan Kubb. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ FileList['tasks/**/*.rake'].each { |task| import task }
data/dm-core.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ require 'English'
2
+ require File.expand_path('lib/dm-core/version', __dir__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'sbf-dm-core'
6
+ gem.version = DataMapper::VERSION.dup
7
+ gem.required_ruby_version = '>= 2.7.8'
8
+ gem.authors = ['Dan Kubb']
9
+ gem.email = %w(dan.kubb@gmail.com)
10
+ gem.summary = 'DataMapper core library'
11
+ gem.description = 'DataMapper core library where one row in the data-store should equal one object reference. ' \
12
+ 'Pretty simple idea. Pretty profound impact.'
13
+ gem.license = 'Nonstandard'
14
+ gem.homepage = 'https://github.com/firespring/dm-core'
15
+
16
+ gem.require_paths = %w(lib)
17
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
18
+ gem.extra_rdoc_files = %w(LICENSE README.md)
19
+
20
+ gem.add_runtime_dependency('addressable', '~> 2.3', '>= 2.3.5')
21
+ end
@@ -0,0 +1,233 @@
1
+ module DataMapper
2
+ module Adapters
3
+ # Specific adapters extend this class and implement
4
+ # methods for creating, reading, updating and deleting records.
5
+ #
6
+ # Adapters may only implement method for reading or (less common case)
7
+ # writing. Read only adapter may be useful when one needs to work
8
+ # with legacy data that should not be changed or web services that
9
+ # only provide read access to data (from Wordnet and Medline to
10
+ # Atom and RSS syndication feeds)
11
+ #
12
+ # Note that in case of adapters to relational databases it makes
13
+ # sense to inherit from DataObjectsAdapter class.
14
+ class AbstractAdapter
15
+ include DataMapper::Assertions
16
+ extend Equalizer, DataMapper::Assertions
17
+
18
+ equalize :name, :options, :resource_naming_convention, :field_naming_convention
19
+
20
+ # @api semipublic
21
+ def self.descendants
22
+ @descendants ||= DescendantSet.new
23
+ end
24
+
25
+ # @api private
26
+ def self.inherited(descendant)
27
+ descendants << descendant
28
+ super
29
+ end
30
+
31
+ # Adapter name
32
+ #
33
+ # @example
34
+ # adapter.name # => :default
35
+ #
36
+ # Note that when you use
37
+ #
38
+ # DataMapper.setup(:default, 'postgres://postgres@127.0.0.1/dm_core_test')
39
+ #
40
+ # the adapter name is currently set to :default
41
+ #
42
+ # @return [Symbol]
43
+ # the adapter name
44
+ #
45
+ # @api semipublic
46
+ attr_reader :name
47
+
48
+ # Options with which adapter was set up
49
+ #
50
+ # @example
51
+ # adapter.options # => { :adapter => 'yaml', :path => '/tmp' }
52
+ #
53
+ # @return [Hash]
54
+ # adapter configuration options
55
+ #
56
+ # @api semipublic
57
+ attr_reader :options
58
+
59
+ # A callable object returning a naming convention for model storage
60
+ #
61
+ # @example
62
+ # adapter.resource_naming_convention # => Proc for model storage name
63
+ #
64
+ # @return [#call]
65
+ # object to return the naming convention for each model
66
+ #
67
+ # @api semipublic
68
+ attr_accessor :resource_naming_convention
69
+
70
+ # A callable object returning a naming convention for property fields
71
+ #
72
+ # @example
73
+ # adapter.field_naming_convention # => Proc for field name
74
+ #
75
+ # @return [#call]
76
+ # object to return the naming convention for each field
77
+ #
78
+ # @api semipublic
79
+ attr_accessor :field_naming_convention
80
+
81
+ # Persists one or many new resources
82
+ #
83
+ # @example
84
+ # adapter.create(collection) # => 1
85
+ #
86
+ # Adapters provide specific implementation of this method
87
+ #
88
+ # @param [Enumerable<Resource>] resources
89
+ # The list of resources (model instances) to create
90
+ #
91
+ # @return [Integer]
92
+ # The number of records that were actually saved into the data-store
93
+ #
94
+ # @api semipublic
95
+ def create(resources)
96
+ raise NotImplementedError, "#{self.class}#create not implemented"
97
+ end
98
+
99
+ # Reads one or many resources from a datastore
100
+ #
101
+ # @example
102
+ # adapter.read(query) # => [ { 'name' => 'Dan Kubb' } ]
103
+ #
104
+ # Adapters provide specific implementation of this method
105
+ #
106
+ # @param [Query] query
107
+ # the query to match resources in the datastore
108
+ #
109
+ # @return [Enumerable<Hash>]
110
+ # an array of hashes to become resources
111
+ #
112
+ # @api semipublic
113
+ def read(query)
114
+ raise NotImplementedError, "#{self.class}#read not implemented"
115
+ end
116
+
117
+ # Updates one or many existing resources
118
+ #
119
+ # @example
120
+ # adapter.update(attributes, collection) # => 1
121
+ #
122
+ # Adapters provide specific implementation of this method
123
+ #
124
+ # @param [Hash(Property => Object)] attributes
125
+ # hash of attribute values to set, keyed by Property
126
+ # @param [Collection] collection
127
+ # collection of records to be updated
128
+ #
129
+ # @return [Integer]
130
+ # the number of records updated
131
+ #
132
+ # @api semipublic
133
+ def update(attributes, collection)
134
+ raise NotImplementedError, "#{self.class}#update not implemented"
135
+ end
136
+
137
+ # Deletes one or many existing resources
138
+ #
139
+ # @example
140
+ # adapter.delete(collection) # => 1
141
+ #
142
+ # Adapters provide specific implementation of this method
143
+ #
144
+ # @param [Collection] collection
145
+ # collection of records to be deleted
146
+ #
147
+ # @return [Integer]
148
+ # the number of records deleted
149
+ #
150
+ # @api semipublic
151
+ def delete(collection)
152
+ raise NotImplementedError, "#{self.class}#delete not implemented"
153
+ end
154
+
155
+ # Create a Query object or subclass.
156
+ #
157
+ # Alter this method if you'd like to return an adapter specific Query subclass.
158
+ #
159
+ # @param [Repository] repository
160
+ # the Repository to retrieve results from
161
+ # @param [Model] model
162
+ # the Model to retrieve results from
163
+ # @param [Hash] options
164
+ # the conditions and scope
165
+ #
166
+ # @return [Query]
167
+ #
168
+ # @api semipublic
169
+ #--
170
+ # TODO: DataObjects::Connection.create_command style magic (Adapter)::Query?
171
+ def new_query(repository, model, options = {})
172
+ Query.new(repository, model, options)
173
+ end
174
+
175
+ # Set the serial value of the Resource
176
+ #
177
+ # @param [Resource] resource
178
+ # the resource to set the serial property in
179
+ # @param [Integer] next_id
180
+ # the identifier to set in the resource
181
+ #
182
+ # @return [undefined]
183
+ #
184
+ # @api semipublic
185
+ protected def initialize_serial(resource, next_id)
186
+ return unless (serial = resource.model.serial(name))
187
+ return unless serial.get!(resource).nil?
188
+
189
+ serial.set!(resource, next_id)
190
+
191
+ # TODO: replace above with this, once
192
+ # specs can handle random, non-sequential ids
193
+ # serial.set!(resource, rand(2**32))
194
+ end
195
+
196
+ # Translate the attributes into a Hash with the field as the key
197
+ #
198
+ # @example
199
+ # attributes = { User.properties[:name] => 'Dan Kubb' }
200
+ # adapter.attributes_as_fields(attributes) # => { 'name' => 'Dan Kubb' }
201
+ #
202
+ # @param [Hash] attributes
203
+ # the attributes with the Property as the key
204
+ #
205
+ # @return [Hash]
206
+ # the attributes with the Property#field as the key
207
+ #
208
+ # @api semipublic
209
+ protected def attributes_as_fields(attributes)
210
+ attributes.to_h { |property, value| [property.field, property.dump(value)] }
211
+ end
212
+
213
+ # Initialize an AbstractAdapter instance
214
+ #
215
+ # @param [Symbol] name
216
+ # the adapter repository name
217
+ # @param [Hash] options
218
+ # the adapter configuration options
219
+ #
220
+ # @return [undefined]
221
+ #
222
+ # @api semipublic
223
+ private def initialize(name, options)
224
+ @name = name
225
+ @options = options.dup.freeze
226
+ @resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
227
+ @field_naming_convention = NamingConventions::Field::Underscored
228
+ end
229
+ end
230
+
231
+ const_added(:AbstractAdapter)
232
+ end
233
+ end
@@ -0,0 +1,110 @@
1
+ module DataMapper
2
+ module Adapters
3
+ # This is probably the simplest functional adapter possible. It simply
4
+ # stores and queries from a hash containing the model classes as keys,
5
+ # and an array of hashes. It is not persistent whatsoever; when the Ruby
6
+ # process finishes, everything that was stored it lost. However, it doesn't
7
+ # require any other external libraries, such as data_objects, so it is ideal
8
+ # for writing specs against. It also serves as an excellent example for
9
+ # budding adapter developers, so it is critical that it remains well documented
10
+ # and up to date.
11
+ class InMemoryAdapter < AbstractAdapter
12
+ # Used by DataMapper to put records into a data-store: "INSERT" in SQL-speak.
13
+ # It takes an array of the resources (model instances) to be saved. Resources
14
+ # each have a key that can be used to quickly look them up later without
15
+ # searching, if the adapter supports it.
16
+ #
17
+ # @param [Enumerable(Resource)] resources
18
+ # The set of resources (model instances)
19
+ #
20
+ # @api semipublic
21
+ def create(resources)
22
+ records = records_for(resources.first&.model)
23
+
24
+ resources.each do |resource|
25
+ initialize_serial(resource, records.size.succ)
26
+ records << attributes_as_fields(resource.attributes(nil))
27
+ end
28
+ end
29
+
30
+ # Looks up one record or a collection of records from the data-store:
31
+ # "SELECT" in SQL.
32
+ #
33
+ # @param [Query] query
34
+ # The query to be used to search for the resources
35
+ #
36
+ # @return [Array]
37
+ # An Array of Hashes containing the key-value pairs for
38
+ # each record
39
+ #
40
+ # @api semipublic
41
+ def read(query)
42
+ query.filter_records(records_for(query.model).dup)
43
+ end
44
+
45
+ # Used by DataMapper to update the attributes on existing records in a
46
+ # data-store: "UPDATE" in SQL-speak. It takes a hash of the attributes
47
+ # to update with, as well as a collection object that specifies which resources
48
+ # should be updated.
49
+ #
50
+ # @param [Hash] attributes
51
+ # A set of key-value pairs of the attributes to update the resources with.
52
+ # @param [DataMapper::Collection] collection
53
+ # The collection of resources to update.
54
+ #
55
+ # @api semipublic
56
+ def update(attributes, collection)
57
+ attributes = attributes_as_fields(attributes)
58
+ read(collection.query)&.each { |record| record.update(attributes) }&.size
59
+ end
60
+
61
+ # Destroys all the records matching the given query. "DELETE" in SQL.
62
+ #
63
+ # @param [DataMapper::Collection] collection
64
+ # The collection of resources to delete.
65
+ #
66
+ # @return [Integer]
67
+ # The number of records that were deleted.
68
+ #
69
+ # @api semipublic
70
+ def delete(collection)
71
+ records = records_for(collection.model)
72
+ records_to_delete = collection.query.filter_records(records.dup)
73
+ records.replace(records - records_to_delete)
74
+ records_to_delete&.size
75
+ end
76
+
77
+ # TODO: consider proper automigrate functionality
78
+ def reset
79
+ @records = {}
80
+ end
81
+
82
+ # Make a new instance of the adapter. The @records ivar is the 'data-store'
83
+ # for this adapter. It is not shared amongst multiple incarnations of this
84
+ # adapter, eg DataMapper.setup(:default, :adapter => :in_memory);
85
+ # DataMapper.setup(:alternate, :adapter => :in_memory) do not share the
86
+ # data-store between them.
87
+ #
88
+ # @param [String, Symbol] name
89
+ # The name of the Repository using this adapter.
90
+ # @param [String, Hash] options
91
+ # The connection uri string, or a hash of options to set up
92
+ # the adapter
93
+ #
94
+ # @api semipublic
95
+ private def initialize(name, options = {})
96
+ super
97
+ @records = {}
98
+ end
99
+
100
+ # All the records we're storing. This method will look them up by model name
101
+ #
102
+ # @api private
103
+ private def records_for(model)
104
+ @records[model.storage_name(name)] ||= []
105
+ end
106
+ end
107
+
108
+ const_added(:InMemoryAdapter)
109
+ end
110
+ end