dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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