dm-core 0.9.11 → 0.10.0

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 (194) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -50
  5. data/Manifest.txt +66 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +6 -7
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/deps.rip +2 -0
  12. data/dm-core.gemspec +11 -15
  13. data/lib/dm-core.rb +105 -110
  14. data/lib/dm-core/adapters.rb +135 -16
  15. data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
  16. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  18. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  19. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  22. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  23. data/lib/dm-core/associations/many_to_many.rb +372 -90
  24. data/lib/dm-core/associations/many_to_one.rb +220 -73
  25. data/lib/dm-core/associations/one_to_many.rb +319 -255
  26. data/lib/dm-core/associations/one_to_one.rb +66 -53
  27. data/lib/dm-core/associations/relationship.rb +561 -156
  28. data/lib/dm-core/collection.rb +1101 -379
  29. data/lib/dm-core/core_ext/kernel.rb +12 -0
  30. data/lib/dm-core/core_ext/symbol.rb +10 -0
  31. data/lib/dm-core/identity_map.rb +4 -34
  32. data/lib/dm-core/migrations.rb +1283 -0
  33. data/lib/dm-core/model.rb +570 -369
  34. data/lib/dm-core/model/descendant_set.rb +81 -0
  35. data/lib/dm-core/model/hook.rb +45 -0
  36. data/lib/dm-core/model/is.rb +32 -0
  37. data/lib/dm-core/model/property.rb +247 -0
  38. data/lib/dm-core/model/relationship.rb +335 -0
  39. data/lib/dm-core/model/scope.rb +90 -0
  40. data/lib/dm-core/property.rb +808 -273
  41. data/lib/dm-core/property_set.rb +141 -98
  42. data/lib/dm-core/query.rb +1037 -483
  43. data/lib/dm-core/query/conditions/comparison.rb +872 -0
  44. data/lib/dm-core/query/conditions/operation.rb +221 -0
  45. data/lib/dm-core/query/direction.rb +43 -0
  46. data/lib/dm-core/query/operator.rb +84 -0
  47. data/lib/dm-core/query/path.rb +138 -0
  48. data/lib/dm-core/query/sort.rb +45 -0
  49. data/lib/dm-core/repository.rb +210 -94
  50. data/lib/dm-core/resource.rb +641 -421
  51. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  52. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  53. data/lib/dm-core/support/chainable.rb +22 -0
  54. data/lib/dm-core/support/deprecate.rb +12 -0
  55. data/lib/dm-core/support/logger.rb +13 -0
  56. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  57. data/lib/dm-core/transaction.rb +333 -92
  58. data/lib/dm-core/type.rb +98 -60
  59. data/lib/dm-core/types/boolean.rb +1 -1
  60. data/lib/dm-core/types/discriminator.rb +34 -20
  61. data/lib/dm-core/types/object.rb +7 -4
  62. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  63. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  64. data/lib/dm-core/types/serial.rb +3 -3
  65. data/lib/dm-core/types/text.rb +3 -4
  66. data/lib/dm-core/version.rb +1 -1
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/migrations_spec.rb +359 -0
  80. data/spec/public/model/relationship_spec.rb +924 -0
  81. data/spec/public/model_spec.rb +159 -0
  82. data/spec/public/property_spec.rb +829 -0
  83. data/spec/public/resource_spec.rb +71 -0
  84. data/spec/public/sel_spec.rb +44 -0
  85. data/spec/public/setup_spec.rb +145 -0
  86. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  87. data/spec/public/shared/collection_shared_spec.rb +1670 -0
  88. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  89. data/spec/public/shared/resource_shared_spec.rb +924 -0
  90. data/spec/public/shared/sel_shared_spec.rb +112 -0
  91. data/spec/public/transaction_spec.rb +129 -0
  92. data/spec/public/types/discriminator_spec.rb +130 -0
  93. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  94. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  95. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  96. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  97. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  99. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  100. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  101. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  102. data/spec/semipublic/associations_spec.rb +177 -0
  103. data/spec/semipublic/collection_spec.rb +142 -0
  104. data/spec/semipublic/property_spec.rb +61 -0
  105. data/spec/semipublic/query/conditions_spec.rb +528 -0
  106. data/spec/semipublic/query/path_spec.rb +443 -0
  107. data/spec/semipublic/query_spec.rb +2626 -0
  108. data/spec/semipublic/resource_spec.rb +47 -0
  109. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  110. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  111. data/spec/spec.opts +3 -1
  112. data/spec/spec_helper.rb +80 -57
  113. data/tasks/ci.rb +19 -31
  114. data/tasks/dm.rb +43 -48
  115. data/tasks/doc.rb +8 -11
  116. data/tasks/gemspec.rb +5 -5
  117. data/tasks/hoe.rb +15 -16
  118. data/tasks/install.rb +8 -10
  119. metadata +74 -111
  120. data/lib/dm-core/associations.rb +0 -207
  121. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  122. data/lib/dm-core/auto_migrations.rb +0 -105
  123. data/lib/dm-core/dependency_queue.rb +0 -32
  124. data/lib/dm-core/hook.rb +0 -11
  125. data/lib/dm-core/is.rb +0 -16
  126. data/lib/dm-core/logger.rb +0 -232
  127. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  128. data/lib/dm-core/migrator.rb +0 -29
  129. data/lib/dm-core/scope.rb +0 -58
  130. data/lib/dm-core/support.rb +0 -7
  131. data/lib/dm-core/support/array.rb +0 -13
  132. data/lib/dm-core/support/assertions.rb +0 -8
  133. data/lib/dm-core/support/errors.rb +0 -23
  134. data/lib/dm-core/support/kernel.rb +0 -11
  135. data/lib/dm-core/support/symbol.rb +0 -41
  136. data/lib/dm-core/type_map.rb +0 -80
  137. data/lib/dm-core/types.rb +0 -19
  138. data/script/all +0 -4
  139. data/spec/integration/association_spec.rb +0 -1382
  140. data/spec/integration/association_through_spec.rb +0 -203
  141. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  142. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  143. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  144. data/spec/integration/auto_migrations_spec.rb +0 -413
  145. data/spec/integration/collection_spec.rb +0 -1073
  146. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  147. data/spec/integration/dependency_queue_spec.rb +0 -46
  148. data/spec/integration/model_spec.rb +0 -197
  149. data/spec/integration/mysql_adapter_spec.rb +0 -85
  150. data/spec/integration/postgres_adapter_spec.rb +0 -731
  151. data/spec/integration/property_spec.rb +0 -253
  152. data/spec/integration/query_spec.rb +0 -514
  153. data/spec/integration/repository_spec.rb +0 -61
  154. data/spec/integration/resource_spec.rb +0 -513
  155. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  156. data/spec/integration/sti_spec.rb +0 -273
  157. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  158. data/spec/integration/transaction_spec.rb +0 -75
  159. data/spec/integration/type_spec.rb +0 -275
  160. data/spec/lib/logging_helper.rb +0 -18
  161. data/spec/lib/mock_adapter.rb +0 -27
  162. data/spec/lib/model_loader.rb +0 -100
  163. data/spec/lib/publicize_methods.rb +0 -28
  164. data/spec/models/content.rb +0 -16
  165. data/spec/models/vehicles.rb +0 -34
  166. data/spec/models/zoo.rb +0 -48
  167. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  168. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  169. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  170. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  171. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  172. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  173. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  174. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  175. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  176. data/spec/unit/associations/relationship_spec.rb +0 -71
  177. data/spec/unit/associations_spec.rb +0 -242
  178. data/spec/unit/auto_migrations_spec.rb +0 -111
  179. data/spec/unit/collection_spec.rb +0 -182
  180. data/spec/unit/data_mapper_spec.rb +0 -35
  181. data/spec/unit/identity_map_spec.rb +0 -126
  182. data/spec/unit/is_spec.rb +0 -80
  183. data/spec/unit/migrator_spec.rb +0 -33
  184. data/spec/unit/model_spec.rb +0 -321
  185. data/spec/unit/naming_conventions_spec.rb +0 -36
  186. data/spec/unit/property_set_spec.rb +0 -90
  187. data/spec/unit/property_spec.rb +0 -753
  188. data/spec/unit/query_spec.rb +0 -571
  189. data/spec/unit/repository_spec.rb +0 -93
  190. data/spec/unit/resource_spec.rb +0 -649
  191. data/spec/unit/scope_spec.rb +0 -142
  192. data/spec/unit/transaction_spec.rb +0 -493
  193. data/spec/unit/type_map_spec.rb +0 -114
  194. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,159 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ # TODO: move these specs into shared specs for #copy
4
+ describe DataMapper::Model do
5
+ before :all do
6
+ module ::Blog
7
+ class Article
8
+ include DataMapper::Resource
9
+
10
+ property :id, Serial
11
+ property :title, String, :nullable => false
12
+ property :content, Text
13
+ property :subtitle, String
14
+ property :author, String, :nullable => false
15
+
16
+ belongs_to :original, self, :nullable => true
17
+ has n, :revisions, self, :child_key => [ :original_id ]
18
+ has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ]
19
+ end
20
+ end
21
+
22
+ @article_model = Blog::Article
23
+ end
24
+
25
+ supported_by :all do
26
+ before :all do
27
+ @author = 'Dan Kubb'
28
+
29
+ @original = @article_model.create(:title => 'Original Article', :author => @author)
30
+ @article = @article_model.create(:title => 'Sample Article', :content => 'Sample', :original => @original, :author => @author)
31
+ @other = @article_model.create(:title => 'Other Article', :content => 'Other', :author => @author)
32
+ end
33
+
34
+ it { @article_model.should respond_to(:copy) }
35
+
36
+ describe '#copy' do
37
+ with_alternate_adapter do
38
+ describe 'between identical models' do
39
+ before :all do
40
+ @return = @resources = @article_model.copy(@repository.name, @alternate_adapter.name)
41
+ end
42
+
43
+ it 'should return an Enumerable' do
44
+ @return.should be_a_kind_of(Enumerable)
45
+ end
46
+
47
+ it 'should return Resources' do
48
+ @return.each { |resource| resource.should be_a_kind_of(DataMapper::Resource) }
49
+ end
50
+
51
+ it 'should have each Resource set to the expected Repository' do
52
+ @resources.each { |resource| resource.repository.name.should == @alternate_adapter.name }
53
+ end
54
+
55
+ it 'should create the Resources in the expected Repository' do
56
+ @article_model.all(:repository => DataMapper.repository(@alternate_adapter.name)).should == @resources
57
+ end
58
+ end
59
+
60
+ describe 'between different models' do
61
+ before :all do
62
+ @other.destroy
63
+ @article.destroy
64
+ @original.destroy
65
+
66
+ # make sure the default repository is empty
67
+ @article_model.all(:repository => @repository).should be_empty
68
+
69
+ # add an extra property to the alternate model
70
+ DataMapper.repository(@alternate_adapter.name) do
71
+ @article_model.property :status, String, :default => 'new'
72
+ end
73
+
74
+ if @article_model.respond_to?(:auto_migrate!)
75
+ @article_model.auto_migrate!(@alternate_adapter.name)
76
+ end
77
+
78
+ # add new resources to the alternate repository
79
+ DataMapper.repository(@alternate_adapter.name) do
80
+ @heff1 = @article_model.create(:title => 'Alternate Repository', :author => @author)
81
+ end
82
+
83
+ # copy from the alternate to the default repository
84
+ @return = @resources = @article_model.copy(@alternate_adapter.name, :default)
85
+ end
86
+
87
+ it 'should return an Enumerable' do
88
+ @return.should be_a_kind_of(Enumerable)
89
+ end
90
+
91
+ it 'should return Resources' do
92
+ @return.each { |resource| resource.should be_a_kind_of(DataMapper::Resource) }
93
+ end
94
+
95
+ it 'should have each Resource set to the expected Repository' do
96
+ @resources.each { |resource| resource.repository.name.should == :default }
97
+ end
98
+
99
+ it 'should create the Resources in the expected Repository' do
100
+ @article_model.all.should == @resources
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ describe DataMapper::Model do
109
+ extend DataMapper::Spec::CollectionHelpers::GroupMethods
110
+
111
+ self.loaded = false
112
+
113
+ before :all do
114
+ module ::Blog
115
+ class Article
116
+ include DataMapper::Resource
117
+
118
+ property :id, Serial
119
+ property :title, String
120
+ property :content, Text
121
+ property :subtitle, String
122
+
123
+ belongs_to :original, self, :nullable => true
124
+ has n, :revisions, self, :child_key => [ :original_id ]
125
+ has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ]
126
+ has n, :publications, :through => Resource
127
+ end
128
+
129
+ class Publication
130
+ include DataMapper::Resource
131
+
132
+ property :id, Serial
133
+ property :name, String
134
+
135
+ has n, :articles, :through => Resource
136
+ end
137
+ end
138
+
139
+ @article_model = Blog::Article
140
+ @publication_model = Blog::Publication
141
+ end
142
+
143
+ supported_by :all do
144
+ # model cannot be a kicker
145
+ def should_not_be_a_kicker; end
146
+
147
+ def model?; true end
148
+
149
+ before :all do
150
+ @articles = @article_model
151
+
152
+ @original = @articles.create(:title => 'Original Article')
153
+ @article = @articles.create(:title => 'Sample Article', :content => 'Sample', :original => @original)
154
+ @other = @articles.create(:title => 'Other Article', :content => 'Other')
155
+ end
156
+
157
+ it_should_behave_like 'Finder Interface'
158
+ end
159
+ end
@@ -0,0 +1,829 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
3
+
4
+ describe DataMapper::Property do
5
+
6
+ # define the model prior to supported_by
7
+ before :all do
8
+ class ::Track
9
+ include DataMapper::Resource
10
+
11
+ property :id, Serial
12
+ property :artist, String, :lazy => false, :index => :artist_album
13
+ property :title, String, :field => 'name', :index => true
14
+ property :album, String, :index => :artist_album
15
+ property :musicbrainz_hash, String, :unique => true, :unique_index => true
16
+ end
17
+
18
+ class ::Image
19
+ include DataMapper::Resource
20
+
21
+ property :md5hash, String, :key => true, :length => 32
22
+ property :title, String, :nullable => false, :unique => true
23
+ property :description, Text, :length => 1..1024, :lazy => [ :detail ]
24
+
25
+ property :format, String, :default => 'jpeg'
26
+ # WxH, stored as a dumped Ruby pair
27
+ property :size, Object
28
+ property :filesize, Float
29
+ property :width, Integer
30
+ property :quality, BigDecimal
31
+
32
+ property :taken_on, Date
33
+ property :taken_at, Time, :default => lambda { |resource, property| Time.now }
34
+ property :retouched_at, DateTime
35
+ property :type, Class
36
+ property :visible, Boolean, :default => true
37
+ end
38
+ end
39
+
40
+ supported_by :all do
41
+ describe '#field' do
42
+ it 'returns @field value if it is present' do
43
+ Track.properties[:title].field.should eql('name')
44
+ end
45
+
46
+ it 'returns field for specific repository when it is present'
47
+
48
+ it 'sets field value using field naming convention on first reference'
49
+ end
50
+
51
+ describe '#custom?' do
52
+ it 'is true for custom type fields (not provided by dm-core)'
53
+
54
+ it 'is false for core type fields (provided by dm-core)'
55
+ end
56
+
57
+ describe '#default_for' do
58
+ it 'returns default value for non-callables' do
59
+ Image.properties[:format].default_for(Image.new).should == 'jpeg'
60
+ end
61
+
62
+ it 'returns result of a call for callable values' do
63
+ Image.properties[:taken_at].default_for(Image.new).year.should == Time.now.year
64
+ end
65
+ end
66
+
67
+ describe '#eql?' do
68
+ it 'is true for properties with the same model and name' do
69
+ Track.properties[:title].should eql(Track.properties[:title])
70
+ end
71
+
72
+
73
+ it 'is false for properties of different models' do
74
+ Track.properties[:title].should_not eql(Image.properties[:title])
75
+ end
76
+
77
+ it 'is false for properties with different names' do
78
+ Track.properties[:title].should_not eql(Track.properties[:id])
79
+ end
80
+ end
81
+
82
+ describe '#get' do
83
+ before :all do
84
+ @image = Image.create(:md5hash => '5268f0f3f452844c79843e820f998869',
85
+ :title => 'Rome at the sunset',
86
+ :description => 'Just wow')
87
+
88
+ @image.should be_saved
89
+
90
+ @image = Image.first(:fields => [ :md5hash, :title ], :md5hash => @image.md5hash)
91
+ end
92
+
93
+ it 'triggers loading for lazy loaded properties' do
94
+ Image.properties[:description].get(@image)
95
+ Image.properties[:description].loaded?(@image).should be(true)
96
+ end
97
+
98
+ it 'assigns loaded value to @ivar' do
99
+ Image.properties[:description].get(@image)
100
+ @image.instance_variable_get(:@description).should == 'Just wow'
101
+ end
102
+
103
+ it 'sets default value for new records with nil value' do
104
+ Image.properties[:format].get(@image).should == 'jpeg'
105
+ end
106
+
107
+ it 'returns property value' do
108
+ Image.properties[:description].get(@image).should == 'Just wow'
109
+ end
110
+ end
111
+
112
+ describe '#get!' do
113
+ before :all do
114
+ @image = Image.new
115
+
116
+ # now some dark Ruby magic
117
+ @image.instance_variable_set(:@description, 'Is set by magic')
118
+ end
119
+
120
+ it 'gets instance variable value from the resource directly' do
121
+ # if you know a better way to test direct instance variable access,
122
+ # go ahead and make changes to this example
123
+ Image.properties[:description].get!(@image).should == 'Is set by magic'
124
+ end
125
+ end
126
+
127
+ describe '#index' do
128
+ it 'returns true when property has an index' do
129
+ Track.properties[:title].index.should be_true
130
+ end
131
+
132
+ it 'returns index name when property has a named index' do
133
+ Track.properties[:album].index.should eql(:artist_album)
134
+ end
135
+
136
+ it 'returns nil when property has no index' do
137
+ Track.properties[:musicbrainz_hash].index.should be_nil
138
+ end
139
+ end
140
+
141
+ describe '#initialize' do
142
+ describe 'when tracking strategy is explicitly given' do
143
+ it 'uses tracking strategy from options'
144
+ end
145
+
146
+ describe 'when custom type has tracking stragegy' do
147
+ it 'uses tracking strategy from type'
148
+ end
149
+ end
150
+
151
+ describe '#inspect' do
152
+ before :all do
153
+ @str = Track.properties[:title].inspect
154
+ end
155
+
156
+ it 'features model name' do
157
+ @str.should =~ /@model=Track/
158
+ end
159
+
160
+ it 'features property name' do
161
+ @str.should =~ /@name=:title/
162
+ end
163
+ end
164
+
165
+ describe '#key?' do
166
+ describe 'returns true when property is a ' do
167
+ it 'serial key' do
168
+ Track.properties[:id].key?.should be_true
169
+ end
170
+ it 'natural key' do
171
+ Image.properties[:md5hash].key?.should be_true
172
+ end
173
+ end
174
+
175
+ it 'returns true when property is a part of composite key'
176
+
177
+ it 'returns false when property does not relate to a key' do
178
+ Track.properties[:title].key?.should be_false
179
+ end
180
+ end
181
+
182
+ describe '#lazy?' do
183
+ it 'returns true when property is lazy loaded' do
184
+ Image.properties[:description].lazy?.should be_true
185
+ end
186
+
187
+ it 'returns false when property is not lazy loaded' do
188
+ Track.properties[:artist].lazy?.should be_false
189
+ end
190
+ end
191
+
192
+ describe '#length' do
193
+ it 'returns upper bound for Range values' do
194
+ Image.properties[:description].length.should eql(1024)
195
+ end
196
+
197
+ it 'returns value as is for integer values' do
198
+ Image.properties[:md5hash].length.should eql(32)
199
+ end
200
+ end
201
+
202
+ describe '#min' do
203
+ describe 'when :min and :max options not provided to constructor' do
204
+ before do
205
+ @property = Image.property(:integer_with_nil_min, Integer)
206
+ end
207
+
208
+ it 'should be nil' do
209
+ @property.min.should be_nil
210
+ end
211
+ end
212
+
213
+ describe 'when :min option not provided to constructor, but :max is provided' do
214
+ before do
215
+ @property = Image.property(:integer_with_default_min, Integer, :max => 1)
216
+ end
217
+
218
+ it 'should be the default value' do
219
+ @property.min.should == 0
220
+ end
221
+ end
222
+
223
+ describe 'when :min and :max options provided to constructor' do
224
+ before do
225
+ @min = 1
226
+ @property = Image.property(:integer_with_explicit_min, Integer, :min => @min, :max => 2)
227
+ end
228
+
229
+ it 'should be the expected value' do
230
+ @property.min.should == @min
231
+ end
232
+ end
233
+ end
234
+
235
+ describe '#max' do
236
+ describe 'when :min and :max options not provided to constructor' do
237
+ before do
238
+ @property = Image.property(:integer_with_nil_max, Integer)
239
+ end
240
+
241
+ it 'should be nil' do
242
+ @property.max.should be_nil
243
+ end
244
+ end
245
+
246
+ describe 'when :max option not provided to constructor, but :min is provided' do
247
+ before do
248
+ @property = Image.property(:integer_with_default_max, Integer, :min => 1)
249
+ end
250
+
251
+ it 'should be the default value' do
252
+ @property.max.should == 2**31-1
253
+ end
254
+ end
255
+
256
+ describe 'when :min and :max options provided to constructor' do
257
+ before do
258
+ @max = 2
259
+ @property = Image.property(:integer_with_explicit_max, Integer, :min => 1, :max => @max)
260
+ end
261
+
262
+ it 'should be the expected value' do
263
+ @property.max.should == @max
264
+ end
265
+ end
266
+ end
267
+
268
+ describe '#nullable?' do
269
+ it 'returns true when property can accept nil as its value' do
270
+ Track.properties[:artist].nullable?.should be_true
271
+ end
272
+
273
+ it 'returns false when property nil value is prohibited for this property' do
274
+ Image.properties[:title].nullable?.should be_false
275
+ end
276
+ end
277
+
278
+ describe '#serial?' do
279
+ it 'returns true when property is serial (auto incrementing)' do
280
+ Track.properties[:id].serial?.should be_true
281
+ end
282
+
283
+ it 'returns false when property is NOT serial (auto incrementing)' do
284
+ Image.properties[:md5hash].serial?.should be_false
285
+ end
286
+ end
287
+
288
+ # What's going on here:
289
+ #
290
+ # we first set original value and make an assertion on it
291
+ # then we try to set it again, which clears original value
292
+ # (since original value is set, property is no longer dirty)
293
+ describe '#set_original_value' do
294
+ before :all do
295
+ @image = Image.create(:md5hash => '5268f0f3f452844c79843e820f998869',
296
+ :title => 'Rome at the sunset',
297
+ :description => 'Just wow')
298
+ @image.reload
299
+ @property = Image.properties[:title]
300
+ end
301
+
302
+ describe 'when value changes' do
303
+ before :all do
304
+ @property.set_original_value(@image, 'Rome at the sunset')
305
+ end
306
+
307
+ it 'sets original value of the property' do
308
+ @image.original_attributes[@property].should == 'Rome at the sunset'
309
+ end
310
+ end
311
+
312
+ describe 'when value stays the same' do
313
+ before :all do
314
+ @property.set_original_value(@image, 'Rome at the sunset')
315
+ end
316
+
317
+ it 'only sets original value when it has changed' do
318
+ @property.set_original_value(@image, 'Rome at the sunset')
319
+ @image.original_attributes[@property].should be_blank
320
+ end
321
+ end
322
+ end
323
+
324
+ describe '#set' do
325
+ before :all do
326
+ # keep in mind we must run these examples with a
327
+ # saved model instance
328
+ @image = Image.create(:md5hash => '5268f0f3f452844c79843e820f998869',
329
+ :title => 'Rome at the sunset',
330
+ :description => 'Just wow')
331
+ @image.reload
332
+ @property = Image.properties[:title]
333
+ end
334
+
335
+ it 'triggers lazy loading for given resource'
336
+
337
+ it 'type casts given value' do
338
+ # set it to a float
339
+ @property.set(@image, 1.0)
340
+ # get a string that has been typecasted
341
+ @image.title.should == '1.0'
342
+ end
343
+
344
+ it 'stores original value' do
345
+ @property.set(@image, 'Updated value')
346
+ @image.original_attributes[@property].should == 'Rome at the sunset'
347
+ end
348
+
349
+ it 'sets new property value' do
350
+ @property.set(@image, 'Updated value')
351
+ @image.title.should == 'Updated value'
352
+ end
353
+ end
354
+
355
+ describe '#set!' do
356
+ before :all do
357
+ @image = Image.new(:md5hash => '5268f0f3f452844c79843e820f998869',
358
+ :title => 'Rome at the sunset',
359
+ :description => 'Just wow')
360
+
361
+ @property = Image.properties[:title]
362
+ end
363
+
364
+ it 'directly sets instance variable on given resource' do
365
+ @property.set!(@image, 'Set with dark Ruby magic')
366
+ @image.title.should == 'Set with dark Ruby magic'
367
+ end
368
+ end
369
+
370
+ describe '#typecast' do
371
+ describe "when type is able to do typecasting on it's own" do
372
+ it 'delegates all the work to the type'
373
+ end
374
+
375
+ describe 'when value is nil' do
376
+ it 'returns value unchanged' do
377
+ Image.properties[:size].typecast(nil).should be(nil)
378
+ end
379
+ end
380
+
381
+ describe 'when value is a Ruby primitive' do
382
+ it 'returns value unchanged' do
383
+ Image.properties[:size].typecast([3200, 2400]).should == [3200, 2400]
384
+ end
385
+ end
386
+
387
+ describe 'when type primitive is a String' do
388
+ before :all do
389
+ @property = Image.properties[:title]
390
+ end
391
+
392
+ it 'returns same value if a string' do
393
+ @value = '1.0'
394
+ @property.typecast(@value).should equal(@value)
395
+ end
396
+
397
+ it 'returns string representation of the new value' do
398
+ @property.typecast(1.0).should eql('1.0')
399
+ end
400
+ end
401
+
402
+ describe 'when type primitive is a Float' do
403
+ before :all do
404
+ @property = Image.properties[:filesize]
405
+ end
406
+
407
+ it 'returns same value if a float' do
408
+ @value = 24.0
409
+ @property.typecast(@value).should equal(@value)
410
+ end
411
+
412
+ it 'returns float representation of a zero string integer' do
413
+ @property.typecast('0').should eql(0.0)
414
+ end
415
+
416
+ it 'returns float representation of a positive string integer' do
417
+ @property.typecast('24').should eql(24.0)
418
+ end
419
+
420
+ it 'returns float representation of a negative string integer' do
421
+ @property.typecast('-24').should eql(-24.0)
422
+ end
423
+
424
+ it 'returns float representation of a zero string float' do
425
+ @property.typecast('0.0').should eql(0.0)
426
+ end
427
+
428
+ it 'returns float representation of a positive string float' do
429
+ @property.typecast('24.35').should eql(24.35)
430
+ end
431
+
432
+ it 'returns float representation of a negative string float' do
433
+ @property.typecast('-24.35').should eql(-24.35)
434
+ end
435
+
436
+ it 'returns float representation of a zero string float, with no leading digits' do
437
+ @property.typecast('.0').should eql(0.0)
438
+ end
439
+
440
+ it 'returns float representation of a positive string float, with no leading digits' do
441
+ @property.typecast('.41').should eql(0.41)
442
+ end
443
+
444
+ it 'returns float representation of a zero integer' do
445
+ @property.typecast(0).should eql(0.0)
446
+ end
447
+
448
+ it 'returns float representation of a positive integer' do
449
+ @property.typecast(24).should eql(24.0)
450
+ end
451
+
452
+ it 'returns float representation of a negative integer' do
453
+ @property.typecast(-24).should eql(-24.0)
454
+ end
455
+
456
+ it 'returns float representation of a zero decimal' do
457
+ @property.typecast(BigDecimal('0.0')).should eql(0.0)
458
+ end
459
+
460
+ it 'returns float representation of a positive decimal' do
461
+ @property.typecast(BigDecimal('24.35')).should eql(24.35)
462
+ end
463
+
464
+ it 'returns float representation of a negative decimal' do
465
+ @property.typecast(BigDecimal('-24.35')).should eql(-24.35)
466
+ end
467
+
468
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
469
+ it "does not typecast non-numeric value #{value.inspect}" do
470
+ @property.typecast(value).should equal(value)
471
+ end
472
+ end
473
+ end
474
+
475
+ describe 'when type primitive is a Integer' do
476
+ before :all do
477
+ @property = Image.properties[:width]
478
+ end
479
+
480
+ it 'returns same value if an integer' do
481
+ @value = 24
482
+ @property.typecast(@value).should equal(@value)
483
+ end
484
+
485
+ it 'returns integer representation of a zero string integer' do
486
+ @property.typecast('0').should eql(0)
487
+ end
488
+
489
+ it 'returns integer representation of a positive string integer' do
490
+ @property.typecast('24').should eql(24)
491
+ end
492
+
493
+ it 'returns integer representation of a negative string integer' do
494
+ @property.typecast('-24').should eql(-24)
495
+ end
496
+
497
+ it 'returns integer representation of a zero string float' do
498
+ @property.typecast('0.0').should eql(0)
499
+ end
500
+
501
+ it 'returns integer representation of a positive string float' do
502
+ @property.typecast('24.35').should eql(24)
503
+ end
504
+
505
+ it 'returns integer representation of a negative string float' do
506
+ @property.typecast('-24.35').should eql(-24)
507
+ end
508
+
509
+ it 'returns integer representation of a zero string float, with no leading digits' do
510
+ @property.typecast('.0').should eql(0)
511
+ end
512
+
513
+ it 'returns integer representation of a positive string float, with no leading digits' do
514
+ @property.typecast('.41').should eql(0)
515
+ end
516
+
517
+ it 'returns integer representation of a zero float' do
518
+ @property.typecast(0.0).should eql(0)
519
+ end
520
+
521
+ it 'returns integer representation of a positive float' do
522
+ @property.typecast(24.35).should eql(24)
523
+ end
524
+
525
+ it 'returns integer representation of a negative float' do
526
+ @property.typecast(-24.35).should eql(-24)
527
+ end
528
+
529
+ it 'returns integer representation of a zero decimal' do
530
+ @property.typecast(BigDecimal('0.0')).should eql(0)
531
+ end
532
+
533
+ it 'returns integer representation of a positive decimal' do
534
+ @property.typecast(BigDecimal('24.35')).should eql(24)
535
+ end
536
+
537
+ it 'returns integer representation of a negative decimal' do
538
+ @property.typecast(BigDecimal('-24.35')).should eql(-24)
539
+ end
540
+
541
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
542
+ it "does not typecast non-numeric value #{value.inspect}" do
543
+ @property.typecast(value).should equal(value)
544
+ end
545
+ end
546
+ end
547
+
548
+ describe 'when type primitive is a BigDecimal' do
549
+ before :all do
550
+ @property = Image.properties[:quality]
551
+ end
552
+
553
+ it 'returns same value if a decimal' do
554
+ @value = BigDecimal('24.0')
555
+ @property.typecast(@value).should equal(@value)
556
+ end
557
+
558
+ it 'returns decimal representation of a zero string integer' do
559
+ @property.typecast('0').should eql(BigDecimal('0.0'))
560
+ end
561
+
562
+ it 'returns decimal representation of a positive string integer' do
563
+ @property.typecast('24').should eql(BigDecimal('24.0'))
564
+ end
565
+
566
+ it 'returns decimal representation of a negative string integer' do
567
+ @property.typecast('-24').should eql(BigDecimal('-24.0'))
568
+ end
569
+
570
+ it 'returns decimal representation of a zero string float' do
571
+ @property.typecast('0.0').should eql(BigDecimal('0.0'))
572
+ end
573
+
574
+ it 'returns decimal representation of a positive string float' do
575
+ @property.typecast('24.35').should eql(BigDecimal('24.35'))
576
+ end
577
+
578
+ it 'returns decimal representation of a negative string float' do
579
+ @property.typecast('-24.35').should eql(BigDecimal('-24.35'))
580
+ end
581
+
582
+ it 'returns decimal representation of a zero string float, with no leading digits' do
583
+ @property.typecast('.0').should eql(BigDecimal('0.0'))
584
+ end
585
+
586
+ it 'returns decimal representation of a positive string float, with no leading digits' do
587
+ @property.typecast('.41').should eql(BigDecimal('0.41'))
588
+ end
589
+
590
+ it 'returns decimal representation of a zero integer' do
591
+ @property.typecast(0).should eql(BigDecimal('0.0'))
592
+ end
593
+
594
+ it 'returns decimal representation of a positive integer' do
595
+ @property.typecast(24).should eql(BigDecimal('24.0'))
596
+ end
597
+
598
+ it 'returns decimal representation of a negative integer' do
599
+ @property.typecast(-24).should eql(BigDecimal('-24.0'))
600
+ end
601
+
602
+ it 'returns decimal representation of a zero float' do
603
+ @property.typecast(0.0).should eql(BigDecimal('0.0'))
604
+ end
605
+
606
+ it 'returns decimal representation of a positive float' do
607
+ @property.typecast(24.35).should eql(BigDecimal('24.35'))
608
+ end
609
+
610
+ it 'returns decimal representation of a negative float' do
611
+ @property.typecast(-24.35).should eql(BigDecimal('-24.35'))
612
+ end
613
+
614
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
615
+ it "does not typecast non-numeric value #{value.inspect}" do
616
+ @property.typecast(value).should equal(value)
617
+ end
618
+ end
619
+ end
620
+
621
+ describe 'when type primitive is a DateTime' do
622
+ before do
623
+ @property = Image.properties[:retouched_at]
624
+ end
625
+
626
+ describe 'and value given as a hash with keys like :year, :month, etc' do
627
+ it 'builds a DateTime instance from hash values' do
628
+ result = @property.typecast(
629
+ 'year' => '2006',
630
+ 'month' => '11',
631
+ 'day' => '23',
632
+ 'hour' => '12',
633
+ 'min' => '0',
634
+ 'sec' => '0'
635
+ )
636
+
637
+ result.should be_kind_of(DateTime)
638
+ result.year.should eql(2006)
639
+ result.month.should eql(11)
640
+ result.day.should eql(23)
641
+ result.hour.should eql(12)
642
+ result.min.should eql(0)
643
+ result.sec.should eql(0)
644
+ end
645
+ end
646
+
647
+ describe 'and value is a string' do
648
+ it 'parses the string' do
649
+ Image.properties[:retouched_at].typecast('Dec, 2006').month.should == 12
650
+ end
651
+ end
652
+
653
+ it 'does not typecast non-datetime values' do
654
+ @property.typecast('not-datetime').should eql('not-datetime')
655
+ end
656
+ end
657
+
658
+ describe 'when type primitive is a Date' do
659
+ before do
660
+ @property = Image.properties[:taken_on]
661
+ end
662
+
663
+ describe 'and value given as a hash with keys like :year, :month, etc' do
664
+ it 'builds a Date instance from hash values' do
665
+ result = @property.typecast(
666
+ 'year' => '2007',
667
+ 'month' => '3',
668
+ 'day' => '25'
669
+ )
670
+
671
+ result.should be_kind_of(Date)
672
+ result.year.should eql(2007)
673
+ result.month.should eql(3)
674
+ result.day.should eql(25)
675
+ end
676
+ end
677
+
678
+ describe 'and value is a string' do
679
+ it 'parses the string' do
680
+ result = @property.typecast('Dec 20th, 2006')
681
+ result.month.should == 12
682
+ result.day.should == 20
683
+ result.year.should == 2006
684
+ end
685
+ end
686
+
687
+ it 'does not typecast non-date values' do
688
+ @property.typecast('not-date').should eql('not-date')
689
+ end
690
+ end
691
+
692
+ describe 'when type primitive is a Time' do
693
+ before do
694
+ @property = Image.properties[:taken_at]
695
+ end
696
+
697
+ describe 'and value given as a hash with keys like :year, :month, etc' do
698
+ it 'builds a Time instance from hash values' do
699
+ result = @property.typecast(
700
+ 'year' => '2006',
701
+ 'month' => '11',
702
+ 'day' => '23',
703
+ 'hour' => '12',
704
+ 'min' => '0',
705
+ 'sec' => '0'
706
+ )
707
+
708
+ result.should be_kind_of(Time)
709
+ result.year.should eql(2006)
710
+ result.month.should eql(11)
711
+ result.day.should eql(23)
712
+ result.hour.should eql(12)
713
+ result.min.should eql(0)
714
+ result.sec.should eql(0)
715
+ end
716
+ end
717
+
718
+ describe 'and value is a string' do
719
+ it 'parses the string' do
720
+ result = @property.typecast('22:24')
721
+ result.hour.should eql(22)
722
+ result.min.should eql(24)
723
+ end
724
+ end
725
+
726
+ it 'does not typecast non-time values' do
727
+ pending 'Time#parse is too permissive' do
728
+ @property.typecast('not-time').should eql('not-time')
729
+ end
730
+ end
731
+ end
732
+
733
+ describe 'when type primitive is a Class' do
734
+ before do
735
+ @property = Image.properties[:type]
736
+ end
737
+
738
+ it 'returns same value if a class' do
739
+ @value = Image
740
+ @property.typecast(@value).should equal(@value)
741
+ end
742
+
743
+ it 'returns the class if found' do
744
+ @property.typecast('Image').should eql(Image)
745
+ end
746
+
747
+ it 'does not typecast non-class values' do
748
+ @property.typecast('NoClass').should eql('NoClass')
749
+ end
750
+ end
751
+
752
+ describe 'when type primitive is a Boolean' do
753
+ before do
754
+ @property = Image.properties[:visible]
755
+ end
756
+
757
+ [ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
758
+ it "returns true when value is #{value.inspect}" do
759
+ @property.typecast(value).should be_true
760
+ end
761
+ end
762
+
763
+ [ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
764
+ it "returns false when value is #{value.inspect}" do
765
+ @property.typecast(value).should be_false
766
+ end
767
+ end
768
+
769
+ [ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
770
+ it "does not typecast value #{value.inspect}" do
771
+ @property.typecast(value).should equal(value)
772
+ end
773
+ end
774
+ end
775
+ end # #typecase
776
+
777
+ describe '#unique?' do
778
+ it 'is true for fields that explicitly given uniq index' do
779
+ Track.properties[:musicbrainz_hash].unique?.should be_true
780
+ end
781
+
782
+ it 'is true for serial fields' do
783
+ pending do
784
+ Track.properties[:title].unique?.should be_true
785
+ end
786
+ end
787
+
788
+ it 'is true for keys' do
789
+ Image.properties[:md5hash].unique?.should be_true
790
+ end
791
+ end
792
+
793
+ describe '#unique_index' do
794
+ it 'returns true when property has unique index' do
795
+ Track.properties[:musicbrainz_hash].unique_index.should be_true
796
+ end
797
+
798
+ it 'returns nil when property has no unique index' do
799
+ Image.properties[:title].unique_index.should be_nil
800
+ end
801
+ end
802
+
803
+ describe '#valid?' do
804
+ describe 'when type primitive is a Boolean' do
805
+ before do
806
+ @property = Image.properties[:visible]
807
+ end
808
+
809
+ [ true, false ].each do |value|
810
+ it "returns true when value is #{value.inspect}" do
811
+ @property.valid?(value).should be_true
812
+ end
813
+ end
814
+
815
+ [ 'true', 'TRUE', '1', 1, 't', 'T', 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
816
+ it "returns false for #{value.inspect}" do
817
+ @property.valid?(value).should be_false
818
+ end
819
+ end
820
+ end
821
+ end
822
+
823
+ describe '#value' do
824
+ it 'returns value for core types'
825
+
826
+ it 'triggers dump operation for custom types'
827
+ end
828
+ end
829
+ end # DataMapper::Property