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.
- data/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- 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 '
|
|
8
|
-
|
|
6
|
+
gem 'addressable', '~>2.0'
|
|
7
|
+
gem 'faker', '~>0.3.1'
|
|
8
|
+
gem 'ruby-prof', '~>0.7.3'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
require 'addressable/uri'
|
|
11
11
|
require 'faker'
|
|
12
|
+
require 'ruby-prof'
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
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,
|
|
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 :
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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.
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
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
|
-
#
|
|
166
|
+
# puts 'Model.new (instantiation)'
|
|
167
|
+
# TIMES.times { Exhibit.new }
|
|
66
168
|
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# end
|
|
169
|
+
# puts 'Model.new (setting attributes)'
|
|
170
|
+
# TIMES.times { Exhibit.new(:name => 'sam', :zoo_id => 1) }
|
|
70
171
|
#
|
|
71
|
-
#
|
|
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
|
-
#
|
|
177
|
+
# TIMES.times { touch_attributes(Exhibit.get(1)) }
|
|
75
178
|
# end
|
|
76
179
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|