datamapper-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 (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  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/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
data/script/profile.rb CHANGED
@@ -1,17 +1,21 @@
1
- #!/usr/bin/env ruby
2
-
3
- require File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core')
1
+ #!/usr/bin/env ruby -KU
4
2
 
3
+ require 'ftools'
5
4
  require 'rubygems'
6
5
 
7
- gem 'ruby-prof', '~>0.7.1'
8
- require 'ruby-prof'
6
+ gem 'addressable', '~>2.0'
7
+ gem 'faker', '~>0.3.1'
8
+ gem 'ruby-prof', '~>0.7.3'
9
9
 
10
- gem 'faker', '~>0.3.1'
10
+ require 'addressable/uri'
11
11
  require 'faker'
12
+ require 'ruby-prof'
12
13
 
13
- OUTPUT = DataMapper.root / 'profile_results.txt'
14
- #OUTPUT = DataMapper.root / 'profile_results.html'
14
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core'))
15
+
16
+ TEXT_OUTPUT = DataMapper.root / 'profile_results.txt'
17
+ HTML_OUTPUT = DataMapper.root / 'profile_results.html'
18
+ CALL_OUTPUT = DataMapper.root / 'profile_results.prof'
15
19
 
16
20
  SOCKET_FILE = Pathname.glob(%w[
17
21
  /opt/local/var/run/mysql5/mysqld.sock
@@ -21,8 +25,33 @@ SOCKET_FILE = Pathname.glob(%w[
21
25
  /var/run/mysqld/mysqld.sock
22
26
  ]).find { |path| path.socket? }
23
27
 
28
+ configuration_options = {
29
+ :adapter => 'mysql',
30
+ :database => 'dm_core_test',
31
+ :host => 'localhost',
32
+ :username => 'root',
33
+ :password => '',
34
+ :socket => SOCKET_FILE,
35
+ }
36
+
24
37
  DataMapper::Logger.new(DataMapper.root / 'log' / 'dm.log', :debug)
25
- DataMapper.setup(:default, "mysql://root@localhost/data_mapper_1?socket=#{SOCKET_FILE}")
38
+ adapter = DataMapper.setup(:default, configuration_options)
39
+
40
+ if configuration_options[:adapter]
41
+ sqlfile = File.join(File.dirname(__FILE__), '..', 'tmp', 'performance.sql')
42
+ mysql_bin = %w[ mysql mysql5 ].select { |bin| `which #{bin}`.length > 0 }
43
+ mysqldump_bin = %w[ mysqldump mysqldump5 ].select { |bin| `which #{bin}`.length > 0 }
44
+ end
45
+
46
+ class User
47
+ include DataMapper::Resource
48
+
49
+ property :id, Serial
50
+ property :name, String
51
+ property :email, String
52
+ property :about, Text, :lazy => false
53
+ property :created_on, Date
54
+ end
26
55
 
27
56
  class Exhibit
28
57
  include DataMapper::Resource
@@ -30,58 +59,160 @@ class Exhibit
30
59
  property :id, Serial
31
60
  property :name, String
32
61
  property :zoo_id, Integer
33
- property :notes, Text, :lazy => true
62
+ property :user_id, Integer
63
+ property :notes, Text, :lazy => false
34
64
  property :created_on, Date
35
- # property :updated_at, DateTime
36
65
 
37
- auto_migrate!
38
- create # create one row for testing
66
+ belongs_to :user
67
+ end
68
+
69
+ DataMapper.auto_migrate!
70
+
71
+ def touch_attributes(*exhibits)
72
+ exhibits.flatten.each do |exhibit|
73
+ exhibit.id
74
+ exhibit.name
75
+ exhibit.created_on
76
+ end
39
77
  end
40
78
 
41
- touch_attributes = lambda do |exhibits|
42
- [*exhibits].each do |exhibit|
79
+ def touch_relationships(*exhibits)
80
+ exhibits.flatten.each do |exhibit|
43
81
  exhibit.id
44
82
  exhibit.name
45
83
  exhibit.created_on
46
- exhibit.updated_at
84
+ exhibit.user
47
85
  end
48
86
  end
49
87
 
50
88
  # RubyProf, making profiling Ruby pretty since 1899!
51
89
  def profile(&b)
52
- result = RubyProf.profile &b
53
- printer = RubyProf::FlatPrinter.new(result)
54
- #printer = RubyProf::GraphHtmlPrinter.new(result)
55
- printer.print(OUTPUT.open('w+'))
90
+ results = RubyProf.profile(&b)
91
+
92
+ TEXT_OUTPUT.open('w+') do |file|
93
+ RubyProf::FlatPrinter.new(results).print(file)
94
+ end
95
+
96
+ HTML_OUTPUT.open('w+') do |file|
97
+ RubyProf::GraphHtmlPrinter.new(results).print(file)
98
+ end
99
+
100
+ CALL_OUTPUT.open('w+') do |file|
101
+ RubyProf::CallTreePrinter.new(results).print(file)
102
+ end
103
+ end
104
+
105
+ c = configuration_options
106
+
107
+ if sqlfile && File.exists?(sqlfile)
108
+ puts "Found data-file. Importing from #{sqlfile}"
109
+ #adapter.execute("LOAD DATA LOCAL INFILE '#{sqlfile}' INTO TABLE exhibits")
110
+ `#{mysql_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} < #{sqlfile}`
111
+ else
112
+ puts 'Generating data for benchmarking...'
113
+
114
+ # pre-compute the insert statements and fake data compilation,
115
+ # so the benchmarks below show the actual runtime for the execute
116
+ # method, minus the setup steps
117
+
118
+ # Using the same paragraph for all exhibits because it is very slow
119
+ # to generate unique paragraphs for all exhibits.
120
+ notes = Faker::Lorem.paragraphs.join($/)
121
+ today = Date.today
122
+
123
+ puts 'Inserting 10,000 users and exhibits...'
124
+ 10_000.times do
125
+ user = User.create(
126
+ :created_on => today,
127
+ :name => Faker::Name.name,
128
+ :email => Faker::Internet.email
129
+ )
130
+
131
+ Exhibit.create(
132
+ :created_on => today,
133
+ :name => Faker::Company.name,
134
+ :user => user,
135
+ :notes => notes,
136
+ :zoo_id => rand(10).ceil
137
+ )
138
+ end
139
+
140
+ if sqlfile
141
+ answer = nil
142
+ until answer && answer[/^$|y|yes|n|no/]
143
+ print('Would you like to dump data into tmp/performance.sql (for faster setup)? [Yn]');
144
+ STDOUT.flush
145
+ answer = gets
146
+ end
147
+
148
+ if answer[/^$|y|yes/]
149
+ File.makedirs(File.dirname(sqlfile))
150
+ #adapter.execute("SELECT * INTO OUTFILE '#{sqlfile}' FROM exhibits;")
151
+ `#{mysqldump_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits users > #{sqlfile}`
152
+ puts "File saved\n"
153
+ end
154
+ end
56
155
  end
57
156
 
157
+ TIMES = 10_000
158
+
159
+ exhibits = Exhibit.all.to_a
160
+
58
161
  profile do
59
- # 10_000.times { touch_attributes[Exhibit.get(1)] }
60
- #
61
- # repository(:default) do
62
- # 10_000.times { touch_attributes[Exhibit.get(1)] }
63
- # end
162
+ # dm_obj = Exhibit.get(1)
163
+ # puts 'Model#id'
164
+ # (TIMES * 100).times { dm_obj.id }
64
165
  #
65
- # 1000.times { touch_attributes[Exhibit.all(:limit => 100)] }
166
+ # puts 'Model.new (instantiation)'
167
+ # TIMES.times { Exhibit.new }
66
168
  #
67
- # repository(:default) do
68
- # 1000.times { touch_attributes[Exhibit.all(:limit => 100)] }
69
- # end
169
+ # puts 'Model.new (setting attributes)'
170
+ # TIMES.times { Exhibit.new(:name => 'sam', :zoo_id => 1) }
70
171
  #
71
- # 10.times { touch_attributes[Exhibit.all(:limit => 10_000)] }
172
+ # puts 'Model.get specific (not cached)'
173
+ # TIMES.times { touch_attributes(Exhibit.get(1)) }
72
174
  #
175
+ # puts 'Model.get specific (cached)'
73
176
  # repository(:default) do
74
- # 10.times { touch_attributes[Exhibit.all(:limit => 10_000)] }
177
+ # TIMES.times { touch_attributes(Exhibit.get(1)) }
75
178
  # end
76
179
 
77
- create_exhibit = {
78
- :name => Faker::Company.name,
79
- :zoo_id => rand(10).ceil,
80
- :notes => Faker::Lorem.paragraphs.join($/),
81
- :created_on => Date.today
82
- }
180
+ puts 'Model.first'
181
+ TIMES.times { touch_attributes(Exhibit.first) }
83
182
 
84
- 1000.times { Exhibit.create(create_exhibit) }
183
+ # puts 'Model.all limit(100)'
184
+ # (TIMES / 10).ceil.times { touch_attributes(Exhibit.all(:limit => 100)) }
185
+ #
186
+ # puts 'Model.all limit(100) with relationship'
187
+ # (TIMES / 10).ceil.times { touch_relationships(Exhibit.all(:limit => 100)) }
188
+ #
189
+ # puts 'Model.all limit(10,000)'
190
+ # (TIMES / 1000).ceil { touch_attributes(Exhibit.all(:limit => 10_000)) }
191
+ #
192
+ # exhibit = {
193
+ # :name => Faker::Company.name,
194
+ # :zoo_id => rand(10).ceil,
195
+ # :notes => Faker::Lorem.paragraphs.join($/),
196
+ # :created_on => Date.today
197
+ # }
198
+ #
199
+ # puts 'Model.create'
200
+ # TIMES.times { Exhibit.create(exhibit) }
201
+ #
202
+ # attrs_first = { :name => 'sam', :zoo_id => 1 }
203
+ # attrs_second = { :name => 'tom', :zoo_id => 1 }
204
+ #
205
+ # puts 'Resource#attributes='
206
+ # TIMES.times { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
207
+ #
208
+ # puts 'Resource#update'
209
+ # TIMES.times { |index| exhibit = exhibits[index]; exhibit.name = 'bob'; exhibit.save }
210
+ #
211
+ # puts 'Resource#destroy'
212
+ # TIMES.times { |index| exhibits[index].destroy }
213
+ #
214
+ # puts 'Model.transaction'
215
+ # TIMES.times { Exhibit.transaction { Exhibit.new } }
85
216
  end
86
217
 
87
218
  puts "Done!"
@@ -0,0 +1,105 @@
1
+ require "benchmark"
2
+
3
+ module DataMapper::Spec
4
+ module AdapterHelpers
5
+ def self.current_adapters
6
+ @current_adapters ||= []
7
+ end
8
+
9
+ def supported_by(*adapters, &block)
10
+ adapters = get_adapters(*adapters)
11
+
12
+ PRIMARY.only(*adapters).each do |adapter, connection_uri|
13
+ # keep track of the current adapters
14
+ AdapterHelpers.current_adapters << adapters
15
+
16
+ describe("with #{adapter}") do
17
+
18
+ before :all do
19
+ # store these in instance vars for the shared adapter specs
20
+ @adapter = DataMapper.setup(:default, connection_uri)
21
+ @repository = DataMapper.repository(@adapter.name)
22
+
23
+ # create all tables and constraints before each spec
24
+ if @repository.respond_to?(:auto_migrate!)
25
+ @repository.auto_migrate!
26
+ end
27
+ end
28
+
29
+ after :all do
30
+ # remove all tables and constraints after each spec
31
+ if DataMapper.respond_to?(:auto_migrate_down!, true)
32
+ DataMapper.send(:auto_migrate_down!, @repository.name)
33
+ end
34
+ end
35
+
36
+ # TODO: add destroy_model_storage and migrations code
37
+ # that removes the YAML file and remove this code
38
+ after :all do
39
+ if defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter)
40
+ descendants = DataMapper::Model.descendants.to_a
41
+ while model = descendants.shift
42
+ descendants.concat(model.descendants.to_a - [ model ])
43
+
44
+ model.default_scope.clear
45
+ model.all(:repository => @repository).destroy!
46
+ end
47
+ end
48
+ end
49
+
50
+ self.instance_eval(&block)
51
+ end
52
+
53
+ AdapterHelpers.current_adapters.pop
54
+ end
55
+ end
56
+
57
+ def with_alternate_adapter(&block)
58
+ adapters = AdapterHelpers.current_adapters.last
59
+
60
+ ALTERNATE.only(*adapters).each do |adapter, connection_uri|
61
+ describe("and #{adapter}") do
62
+
63
+ before :all do
64
+ @alternate_adapter = DataMapper.setup(:alternate, connection_uri)
65
+ @alternate_repository = DataMapper.repository(@alternate_adapter.name)
66
+
67
+ # create all tables and constraints before each spec
68
+ if @alternate_repository.respond_to?(:auto_migrate!)
69
+ @alternate_repository.auto_migrate!
70
+ end
71
+ end
72
+
73
+ after :all do
74
+ # remove all tables and constraints after each spec
75
+ if DataMapper.respond_to?(:auto_migrate_down!, true)
76
+ DataMapper.send(:auto_migrate_down!, @alternate_repository.name)
77
+ end
78
+ end
79
+
80
+ # TODO: add destroy_model_storage and migrations code
81
+ # that removes the YAML file and remove this code
82
+ after :all do
83
+ if defined?(DataMapper::Adapters::YamlAdapter) && @alternate_adapter.kind_of?(DataMapper::Adapters::YamlAdapter)
84
+ descendants = DataMapper::Model.descendants.to_a
85
+ while model = descendants.shift
86
+ descendants.concat(model.descendants.to_a - [ model ])
87
+
88
+ model.default_scope.clear
89
+ model.all(:repository => @alternate_repository).destroy!
90
+ end
91
+ end
92
+ end
93
+
94
+ self.instance_eval(&block)
95
+ end
96
+ end
97
+ end
98
+
99
+ def get_adapters(*adapters)
100
+ adapters.map! { |adapter_name| adapter_name.to_s }
101
+ adapters = ADAPTERS if adapters.include?('all')
102
+ ADAPTERS & adapters
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ module DataMapper::Spec
2
+ module CollectionHelpers
3
+ module GroupMethods
4
+ def self.extended(base)
5
+ base.class_inheritable_accessor :loaded
6
+ base.loaded = false
7
+ end
8
+
9
+ def should_not_be_a_kicker
10
+ unless loaded
11
+ it 'should not be a kicker' do
12
+ @articles.should_not be_loaded
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ class CounterAdapter < DataMapper::Adapters::AbstractAdapter
2
+ instance_methods.each { |method| undef_method method unless %w[ __id__ __send__ send class dup object_id kind_of? instance_of? respond_to? equal? assert_kind_of should should_not instance_variable_set instance_variable_get extend ].include?(method.to_s) }
3
+
4
+ attr_reader :counts
5
+
6
+ def kind_of?(klass)
7
+ super || @adapter.kind_of?(klass)
8
+ end
9
+
10
+ def instance_of?(klass)
11
+ super || @adapter.instance_of?(klass)
12
+ end
13
+
14
+ def respond_to?(method, include_private = false)
15
+ super || @adapter.respond_to?(method, include_private)
16
+ end
17
+
18
+ private
19
+
20
+ def initialize(adapter)
21
+ @counts = Hash.new { |hash, key| hash[key] = 0 }
22
+ @adapter = adapter
23
+ @count = 0
24
+ end
25
+
26
+ def increment_count_for(method)
27
+ @counts[method] += 1
28
+ end
29
+
30
+ def method_missing(method, *args, &block)
31
+ increment_count_for(method)
32
+ @adapter.send(method, *args, &block)
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module DataMapper::Spec
2
+ module PendingHelpers
3
+ def pending_if(message, boolean = true)
4
+ if boolean
5
+ pending(message) { yield }
6
+ else
7
+ yield
8
+ end
9
+ end
10
+
11
+ def rescue_if(message, boolean = true)
12
+ if boolean
13
+ raised = nil
14
+ begin
15
+ yield
16
+ raised = false
17
+ rescue Exception
18
+ raised = true
19
+ end
20
+
21
+ raise 'should have raised' if raised == false
22
+ else
23
+ yield
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec/runner/formatter/base_text_formatter'
2
+
3
+ # Code is based on standard SpecdocFormatter, but will print full error details as soon as they are found.
4
+ # Successful or pending examples are written only as a dot in the output. Header is only printed if errors occur.
5
+ #
6
+ # To use it, add the following to your spec/spec.opts:
7
+ # --require
8
+ # lib/rspec_immediate_feedback_formatter.rb
9
+ # --format
10
+ # Spec::Runner::Formatter::ImmediateFeedbackFormatter
11
+
12
+ module Spec
13
+ module Runner
14
+ module Formatter
15
+ class ImmediateFeedbackFormatter < BaseTextFormatter
16
+
17
+ def add_example_group(example_group)
18
+ super
19
+ @current_group = example_group.description
20
+ end
21
+
22
+ def example_failed(example, counter, failure)
23
+ if @current_group
24
+ output.puts
25
+ output.puts @current_group
26
+ @current_group = nil # only print the group name once
27
+ end
28
+
29
+ message = if failure.expectation_not_met?
30
+ "- #{example.description} (FAILED - #{counter})"
31
+ else
32
+ "- #{example.description} (ERROR - #{counter})"
33
+ end
34
+
35
+ output.puts(red(message))
36
+ dump_failure(counter, failure) # dump stacktrace immediately
37
+ output.flush
38
+ end
39
+
40
+ def example_passed(*)
41
+ output.print green('.')
42
+ output.flush
43
+ end
44
+
45
+ def example_pending(*)
46
+ super
47
+ output.print yellow('*')
48
+ output.flush
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,193 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ share_examples_for 'A Limited Many to Many Collection' do
4
+ describe '#destroy!' do
5
+ describe 'on a limited collection' do
6
+ before :all do
7
+ @other = @articles.create
8
+ @limited = @articles.all(:limit => 1)
9
+
10
+ @return = @limited.destroy!
11
+ end
12
+
13
+ it 'should only remove the join resource for the destroyed resource' do
14
+ @join_model.all.should_not be_empty
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # run the specs once with a loaded association and once not
21
+ [ false, true ].each do |loaded|
22
+ describe 'Many to Many Associations with :through => Resource' do
23
+ extend DataMapper::Spec::CollectionHelpers::GroupMethods
24
+
25
+ self.loaded = loaded
26
+
27
+ # define the model prior to supported_by
28
+ before :all do
29
+ module ::Blog
30
+ class Author
31
+ include DataMapper::Resource
32
+
33
+ property :id, Serial
34
+ property :name, String
35
+
36
+ has n, :articles, :through => Resource
37
+ end
38
+
39
+ class Article
40
+ include DataMapper::Resource
41
+
42
+ property :id, Serial
43
+ property :title, String, :nullable => false
44
+ property :content, Text
45
+ property :subtitle, String
46
+
47
+ has n, :authors, :through => Resource
48
+ belongs_to :original, self, :nullable => true
49
+ has n, :revisions, self, :child_key => [ :original_id ]
50
+ has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ]
51
+ has n, :publications, :through => Resource
52
+ end
53
+
54
+ class Publication
55
+ include DataMapper::Resource
56
+
57
+ property :id, Serial
58
+ property :name, String
59
+
60
+ has n, :articles, :through => Resource
61
+ end
62
+ end
63
+
64
+ @author_model = Blog::Author
65
+ @article_model = Blog::Article
66
+ @publication_model = Blog::Publication
67
+
68
+ # initialize the join model
69
+ Blog::Author.relationships(:default)[:articles].through
70
+
71
+ @join_model = Blog::ArticleAuthor
72
+ end
73
+
74
+ supported_by :all do
75
+ before :all do
76
+ @author = @author_model.create(:name => 'Dan Kubb')
77
+
78
+ @original = @author.articles.create(:title => 'Original Article')
79
+ @article = @author.articles.create(:title => 'Sample Article', :content => 'Sample', :original => @original)
80
+ @other = @author.articles.create(:title => 'Other Article', :content => 'Other')
81
+
82
+ # load the targets without references to a single source
83
+ load_collection = lambda do |query|
84
+ @author_model.get(*@author.key).articles(query)
85
+ end
86
+
87
+ @articles = load_collection.call(:title => 'Sample Article')
88
+ @other_articles = load_collection.call(:title => 'Other Article')
89
+
90
+ @articles.entries if loaded
91
+ end
92
+
93
+ it_should_behave_like 'A public Collection'
94
+ it_should_behave_like 'A public Association Collection'
95
+ it_should_behave_like 'A Collection supporting Strategic Eager Loading' unless loaded
96
+ it_should_behave_like 'Finder Interface'
97
+ it_should_behave_like 'A Limited Many to Many Collection'
98
+ end
99
+ end
100
+
101
+ describe 'Many to Many Associations :through => one_to_many' do
102
+ extend DataMapper::Spec::CollectionHelpers::GroupMethods
103
+
104
+ self.loaded = loaded
105
+
106
+ # define the model prior to supported_by
107
+ before :all do
108
+ module ::Blog
109
+ class Author
110
+ include DataMapper::Resource
111
+
112
+ property :id, Serial
113
+ property :name, String
114
+
115
+ has n, :sites
116
+ has n, :articles, :through => :sites
117
+ end
118
+
119
+ class Site
120
+ include DataMapper::Resource
121
+
122
+ property :name, String, :key => true, :default => 'default'
123
+
124
+ belongs_to :author
125
+ has n, :articles
126
+ end
127
+
128
+ class Article
129
+ include DataMapper::Resource
130
+
131
+ property :id, Serial
132
+ property :title, String, :nullable => false
133
+ property :content, Text
134
+ property :subtitle, String
135
+
136
+ property :site_name, String, :default => 'default'
137
+
138
+ belongs_to :site
139
+ has n, :authors, :through => :site
140
+ belongs_to :original, self, :nullable => true
141
+ has n, :revisions, self, :child_key => [ :original_id ]
142
+ has 1, :previous, self, :child_key => [ :original_id ], :order => [ :id.desc ]
143
+ has n, :publications, :through => Resource
144
+ end
145
+
146
+ class Publication
147
+ include DataMapper::Resource
148
+
149
+ property :id, Serial
150
+ property :name, String
151
+
152
+ has n, :articles, :through => Resource
153
+ end
154
+ end
155
+
156
+ @author_model = Blog::Author
157
+ @article_model = Blog::Article
158
+ @publication_model = Blog::Publication
159
+
160
+ @join_model = Blog::Site
161
+ end
162
+
163
+ supported_by :all do
164
+ before :all do
165
+ @author = @author_model.create(:name => 'Dan Kubb')
166
+
167
+ @original_site = @author.sites.create(:name => 'original')
168
+ @article_site = @author.sites.create(:name => 'article')
169
+ @other_site = @author.sites.create(:name => 'other')
170
+
171
+ @original = @original_site.articles.create(:title => 'Original Article')
172
+ @article = @article_site.articles.create(:title => 'Sample Article', :content => 'Sample', :original => @original)
173
+ @other = @other_site.articles.create(:title => 'Other Article', :content => 'Other')
174
+
175
+ # load the targets without references to a single source
176
+ load_collection = lambda do |query|
177
+ @author_model.get(*@author.key).articles(query)
178
+ end
179
+
180
+ @articles = load_collection.call(:title => 'Sample Article')
181
+ @other_articles = load_collection.call(:title => 'Other Article')
182
+
183
+ @articles.entries if loaded
184
+ end
185
+
186
+ it_should_behave_like 'A public Collection'
187
+ it_should_behave_like 'A public Association Collection'
188
+ it_should_behave_like 'A Collection supporting Strategic Eager Loading' unless loaded
189
+ it_should_behave_like 'Finder Interface'
190
+ it_should_behave_like 'A Limited Many to Many Collection'
191
+ end
192
+ end
193
+ end