smulube-fixture_dependencies 1.2.4
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/LICENSE +19 -0
- data/README +288 -0
- data/lib/fixture_dependencies.rb +218 -0
- data/lib/fixture_dependencies/active_record.rb +43 -0
- data/lib/fixture_dependencies/rspec/sequel.rb +14 -0
- data/lib/fixture_dependencies/sequel.rb +40 -0
- data/lib/fixture_dependencies/test_unit.rb +14 -0
- data/lib/fixture_dependencies/test_unit/rails.rb +33 -0
- data/lib/fixture_dependencies/test_unit/sequel.rb +13 -0
- data/lib/fixture_dependencies_test_help.rb +1 -0
- metadata +68 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2007-2008 Jeremy Evans
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
= fixture_dependencies
|
2
|
+
|
3
|
+
fixture_dependencies is an advanced fixture loader, allowing the loading of
|
4
|
+
models from YAML fixtures, along with their entire dependency graph. It has
|
5
|
+
the following features:
|
6
|
+
|
7
|
+
- Fixtures specify association names instead of foreign keys
|
8
|
+
- Support both Sequel and ActiveRecord
|
9
|
+
- Supports many_to_one/belongs_to, one_to_many/has_many,
|
10
|
+
many_to_many/has_and_belongs_to_many, and has_one associations
|
11
|
+
- Loads a fixture's dependency graph in such a manner that foreign key
|
12
|
+
constraints aren't violated
|
13
|
+
- Has a very simple API (FixtureDependencies.load(:model__fixture))
|
14
|
+
- Handles almost all cyclic dependencies
|
15
|
+
- Includes Rails and Sequel test helpers for Test::Unit (and a Sequel test
|
16
|
+
helper for RSpec) that load fixtures for every test inside a transaction,
|
17
|
+
so fixture data is never left in your database
|
18
|
+
- Adds dynamic fixtures (similar to ActiveRecord) with ERb.
|
19
|
+
|
20
|
+
== Installation
|
21
|
+
|
22
|
+
Original version (lacking ERb support) available here:
|
23
|
+
|
24
|
+
sudo gem install jeremyevans-fixture_dependencies \
|
25
|
+
--source http://gems.github.com
|
26
|
+
|
27
|
+
This version available here:
|
28
|
+
|
29
|
+
sudo gem install smulube-fixture_dependencies \
|
30
|
+
--source http://gems.github.com
|
31
|
+
|
32
|
+
== Source
|
33
|
+
|
34
|
+
Original source is available via github:
|
35
|
+
|
36
|
+
http://github.com/jeremyevans/fixture_dependencies
|
37
|
+
|
38
|
+
You can check it out with git:
|
39
|
+
|
40
|
+
git clone git://github.com/jeremyevans/fixture_dependencies.git
|
41
|
+
|
42
|
+
Patched version is also available via github:
|
43
|
+
|
44
|
+
http://github.com/smulube/fixture_dependencies
|
45
|
+
|
46
|
+
You can check it out with git:
|
47
|
+
|
48
|
+
git clone git://github.com/smulube/fixture_dependencies.git
|
49
|
+
|
50
|
+
|
51
|
+
== Usage
|
52
|
+
|
53
|
+
=== With Rails/ActiveRecord/Test::Unit:
|
54
|
+
|
55
|
+
Add the following to test/test_helper.rb after "require 'test_help'":
|
56
|
+
|
57
|
+
require 'fixture_dependencies/test_unit/rails'
|
58
|
+
|
59
|
+
This overrides the default test helper to load the fixtures inside transactions
|
60
|
+
and to use FixtureDependencies to load the fixtures.
|
61
|
+
|
62
|
+
=== With Sequel/Test::Unit:
|
63
|
+
|
64
|
+
Somewhere before the test code is loaded:
|
65
|
+
|
66
|
+
require 'fixture_dependencies/test_unit/sequel'
|
67
|
+
|
68
|
+
Make sure the test case classes use FixtureDependencies::SequelTestCase:
|
69
|
+
|
70
|
+
class ModelTest < FixtureDependencies::SequelTestCase
|
71
|
+
|
72
|
+
This runs the test cases inside a Sequel transaction.
|
73
|
+
|
74
|
+
=== With Sequel/RSpec:
|
75
|
+
|
76
|
+
Somewhere before the test code is loaded:
|
77
|
+
|
78
|
+
require 'fixture_dependencies/rspec/sequel'
|
79
|
+
|
80
|
+
This runs each spec inside a separate Sequel transaction.
|
81
|
+
|
82
|
+
=== With other testing libraries:
|
83
|
+
|
84
|
+
You can just use FixtureDependencies.load to handle the loading of fixtures.
|
85
|
+
The use of transactions is up to you. One thing you must do if you are
|
86
|
+
not using the rails test helper is to set the fixture path for
|
87
|
+
FixtureDependencies:
|
88
|
+
|
89
|
+
FixtureDependencies.fixture_path = '/path/to/fixtures'
|
90
|
+
|
91
|
+
== Changes to Rails default fixtures:
|
92
|
+
|
93
|
+
fixture_dependencies is designed to require the least possible changes to
|
94
|
+
the default YAML fixtures used by Rails (well, at least Rails 1.2 and earlier).
|
95
|
+
For example, see the following changes:
|
96
|
+
|
97
|
+
OLD NEW
|
98
|
+
asset1: asset1:
|
99
|
+
id: 1 id: 1
|
100
|
+
employee_id: 2 employee: jeremy
|
101
|
+
product_id: 3 product: nx7010
|
102
|
+
vendor_id: 2 vendor: lxg_computers
|
103
|
+
note: in working order note: in working order
|
104
|
+
|
105
|
+
As you can see, you just replace the foreign key attribute and value with the
|
106
|
+
name of the association and the associations name. This assumes you have an
|
107
|
+
employee fixture with a name of jeremy, and products fixture with the name of
|
108
|
+
nx7010, and a vendors fixture with the name lxg_computers.
|
109
|
+
|
110
|
+
Fixture files still use the table_name of the model.
|
111
|
+
|
112
|
+
== Changes to the fixtures Class Method:
|
113
|
+
|
114
|
+
fixture_dependencies can still use the fixtures class method in your test:
|
115
|
+
|
116
|
+
class EmployeeTest < Test::Unit::TestCase
|
117
|
+
fixtures :assets
|
118
|
+
end
|
119
|
+
|
120
|
+
In Rails default testing practices, the arguments to fixtures are table names.
|
121
|
+
fixture_dependencies changes this to underscored model names. If you are using
|
122
|
+
Rails' recommended table practices, this shouldn't make a difference.
|
123
|
+
|
124
|
+
It is recommended that you do not use the fixtures method, and instead load
|
125
|
+
individual fixtures as needed (see below). This makes your tests much more
|
126
|
+
robust, in case you want to add or remove individual fixtures at a later date.
|
127
|
+
|
128
|
+
== Loading individual fixtures with fixtures class method
|
129
|
+
|
130
|
+
There is support for loading individual fixtures (and just their dependencies),
|
131
|
+
using the following syntax:
|
132
|
+
|
133
|
+
class EmployeeTest < Test::Unit::TestCase
|
134
|
+
fixtures :employee__jeremy # Note the double underscore
|
135
|
+
end
|
136
|
+
|
137
|
+
This would load just the jeremy fixture and its dependencies. I find this is
|
138
|
+
much better than loading all fixtures in most of my test suites. Even better
|
139
|
+
is loading just the fixtures you want instead every test method (see below).
|
140
|
+
This leads to the most robust testing.
|
141
|
+
|
142
|
+
== Loading fixtures inside test methods
|
143
|
+
|
144
|
+
I find that it is often better to skip the use of the fixtures method entirely,
|
145
|
+
and load the fixtures I want manually in each test method. This provides for
|
146
|
+
the loosest coupling possible. Here's an example:
|
147
|
+
|
148
|
+
class EmployeeTest < Test::Unit::TestCase
|
149
|
+
def test_employee_name
|
150
|
+
# Load the fixture and return the Employee object
|
151
|
+
employee = load(:employee__jeremy)
|
152
|
+
# Test the employee
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_employees
|
156
|
+
# Load the fixtures and return two Employee objects
|
157
|
+
employee1, employee2 = load(:employees=>[:jeremy, :karl])
|
158
|
+
# Test the employees
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_award_statistics
|
162
|
+
# Load all fixtures in both tables
|
163
|
+
load(:employee_award__jeremy_first, :award__first)
|
164
|
+
# Test the award_statistics method
|
165
|
+
# (which pulls data from the tables loaded above)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
Don't worry about loading the same fixture twice, if a fixture is already
|
170
|
+
loaded, it won't attempt to load it again.
|
171
|
+
|
172
|
+
== one_to_many/many_to_many/has_many/has_and_belongs_to_many assocations
|
173
|
+
|
174
|
+
Here's an example of using has_one (logon_information), has_many (assets), and
|
175
|
+
has_and_belongs_to_many (groups) associations.
|
176
|
+
|
177
|
+
jeremy:
|
178
|
+
id: 2
|
179
|
+
name: Jeremy Evans
|
180
|
+
logon_information: jeremy
|
181
|
+
assets: [asset1, asset2, asset3]
|
182
|
+
groups: [group1]
|
183
|
+
|
184
|
+
logon_information is a has_one association to another table which was split
|
185
|
+
from the employees table due to database security requirements. Assets is a
|
186
|
+
has_many association, where one employee is responsible for the asset.
|
187
|
+
Employees can be a member of multiple groups, and each group can have multiple
|
188
|
+
employees.
|
189
|
+
|
190
|
+
For has_* associations, after fixture_dependencies saves jeremy, it will load
|
191
|
+
and save logon_information (and its dependencies...), it will load each asset
|
192
|
+
in the order specified (and their dependencies...), and it will load all of the
|
193
|
+
groups in the order specified (and their dependencies...). Note that there
|
194
|
+
is only a load order inside a specific association, associations are stored
|
195
|
+
in the same hash as attributes and are loaded in an arbitrary order.
|
196
|
+
|
197
|
+
== many_to_many/has_and_belongs_to_many join table fixtures
|
198
|
+
|
199
|
+
Another change is that Rails defaults allow you to specify habtm join tables in
|
200
|
+
fixtures. That doesn't work with fixture dependencies, as there is no
|
201
|
+
associated model. Instead, you use a has_and_belongs_to_many association name
|
202
|
+
in the the appropriate model fixtures (see above).
|
203
|
+
|
204
|
+
== Cyclic dependencies
|
205
|
+
|
206
|
+
fixture_dependencies handles almost all cyclic dependencies. It handles all
|
207
|
+
has_many, has_one, and habtm cyclic dependencies. It handles all
|
208
|
+
self-referential cyclic dependencies. It handles all belongs_to cyclic
|
209
|
+
dependencies except the case where there is a NOT NULL or validates_presence of
|
210
|
+
constraint on the cyclic dependency's foreign key.
|
211
|
+
|
212
|
+
For example, a case that won't work is when employee belongs_to supervisor
|
213
|
+
(with a NOT NULL or validates_presence_of constraint on supervisor_id), and
|
214
|
+
john is karl's supervisor and karl is john's supervisor. Since you can't create
|
215
|
+
john without a valid supervisor_id, you need to create karl first, but you
|
216
|
+
can't create karl for the same reason (as john doesn't exist yet).
|
217
|
+
|
218
|
+
There isn't a generic way to handle the belongs_to cyclic dependency, as far as
|
219
|
+
I know. Deferring foreign key checks could work, but may not be enabled (and
|
220
|
+
one of the main reasons to use the plugin is that it doesn't require them).
|
221
|
+
For associations like the example above (employee's supervisor is also an
|
222
|
+
employee), setting the foreign_key to the primary key and then changing it
|
223
|
+
later is an option, but database checks may prevent it. For more complex
|
224
|
+
cyclic dependencies involving multiple model classes (employee belongs_to
|
225
|
+
division belongs_to head_of_division when the employee is a member of the
|
226
|
+
division and also the head of the division), even that approach is not
|
227
|
+
possible.
|
228
|
+
|
229
|
+
== Dynamic fixtures with ERb
|
230
|
+
|
231
|
+
Adds support for ERb in the YAML fixtures, as is currently implemented in the
|
232
|
+
standard Rails fixtures.
|
233
|
+
|
234
|
+
<% for i in 1..100 %>
|
235
|
+
fixture_<%= i%>:
|
236
|
+
id: <%= i %>
|
237
|
+
name: guy_<%= i %>
|
238
|
+
<% end %>
|
239
|
+
|
240
|
+
This will create 100 very simple YAML fixtures.
|
241
|
+
|
242
|
+
Using ERb, you can also inject dynamic values into your fixtures with inserts
|
243
|
+
like <%= Date.today.strftime("%Y-%m-%d") %>. This is however a feature to be
|
244
|
+
used with some caution.
|
245
|
+
|
246
|
+
== Known issues
|
247
|
+
|
248
|
+
Currently, the plugin only supports YAML fixtures, but other types of fixtures
|
249
|
+
would be fairly easy to add (send me a patch if you add support for another
|
250
|
+
fixture type).
|
251
|
+
|
252
|
+
The plugin is significantly slower than the default testing method, because it
|
253
|
+
loads all fixtures inside of a transaction (one per test method), where Rails
|
254
|
+
defaults to loading the fixtures once per test suite (outside of a
|
255
|
+
transaction), and only deletes fixtures from a table when overwriting it with
|
256
|
+
new fixtures.
|
257
|
+
|
258
|
+
Instantiated fixtures are not available with this plugin. Instead, you should
|
259
|
+
use load(:model__fixture_name).
|
260
|
+
|
261
|
+
== Troubleshooting
|
262
|
+
|
263
|
+
If you run into problems with loading your fixtures, it can be difficult to see
|
264
|
+
where the problems are. To aid in debugging an error, add the following to
|
265
|
+
test/test_helper.rb:
|
266
|
+
|
267
|
+
FixtureDependencies.verbose = 3
|
268
|
+
|
269
|
+
This will give a verbose description of the loading and saving of fixtures for
|
270
|
+
every test, including the recursive loading of the dependency graph.
|
271
|
+
|
272
|
+
== Similar Ideas
|
273
|
+
|
274
|
+
Rails now supports something similar by default. Honestly, I'm not sure what
|
275
|
+
the differences are.
|
276
|
+
|
277
|
+
fixture_references is a similar plugin. It uses erb inside yaml, and uses the
|
278
|
+
foreign key numbers inside of the association names, which leads me to believe
|
279
|
+
it doesn't support has_* associations.
|
280
|
+
|
281
|
+
== License
|
282
|
+
|
283
|
+
fixture_dependencies is released under the MIT License. See the LICENSE file
|
284
|
+
for details.
|
285
|
+
|
286
|
+
== Author
|
287
|
+
|
288
|
+
Jeremy Evans <code@jeremyevans.net>
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require('sequel/extensions/inflector') unless [:singularize, :camelize, :underscore, :constantize].all?{|meth| "".respond_to?(meth)}
|
2
|
+
|
3
|
+
class FixtureDependencies
|
4
|
+
@fixtures = {}
|
5
|
+
@loaded = {}
|
6
|
+
@verbose = 0
|
7
|
+
|
8
|
+
# Load all record arguments into the database. If a single argument is
|
9
|
+
# given and it corresponds to a single fixture, return the the model
|
10
|
+
# instance corresponding to that fixture. If a single argument if given
|
11
|
+
# and it corresponds to a model, return all model instances corresponding
|
12
|
+
# to that model. If multiple arguments are given, return a list of
|
13
|
+
# model instances (for single fixture arguments) or list of model instances
|
14
|
+
# (for model fixture arguments). If no arguments, return the empty list.
|
15
|
+
# If any of the arguments is a hash, assume the key specifies the model
|
16
|
+
# and the values specify the fixture, and treat it as though individual
|
17
|
+
# symbols specifying both model and fixture were given.
|
18
|
+
#
|
19
|
+
# Examples:
|
20
|
+
# * load(:posts) # All post fixtures, not recommended
|
21
|
+
# * load(:posts, :comments) # All post and comment fixtures, again not recommended
|
22
|
+
# * load(:post__post1) # Just the post fixture named post1
|
23
|
+
# * load(:post__post1, :post__post2) # Post fixtures named post1 and post2
|
24
|
+
# * load(:posts=>[:post1, :post2]) # Post fixtures named post1 and post2
|
25
|
+
# * load(:post__post1, :comment__comment2) # Post fixture named post1 and comment fixture named comment2
|
26
|
+
# * load({:posts=>[:post1, :post2]}, :comment__comment2) # Post fixtures named post1 and post2 and comment fixture named comment2
|
27
|
+
#
|
28
|
+
# This will load the data from the yaml files for each argument whose model
|
29
|
+
# is not already in the fixture hash.
|
30
|
+
def self.load(*records)
|
31
|
+
ret = records.map do |record|
|
32
|
+
if record.is_a?(Hash)
|
33
|
+
record.map do |k, vals|
|
34
|
+
model = k.to_s.singularize
|
35
|
+
vals.map{|v| :"#{model}__#{v}"}
|
36
|
+
end
|
37
|
+
else
|
38
|
+
record
|
39
|
+
end
|
40
|
+
end.flatten.compact.map do |record|
|
41
|
+
model_name, name = split_name(record)
|
42
|
+
if name
|
43
|
+
use(record.to_sym)
|
44
|
+
else
|
45
|
+
model_name = model_name.singularize
|
46
|
+
unless loaded[model_name.to_sym]
|
47
|
+
puts "loading #{model_name}.yml" if verbose > 0
|
48
|
+
load_yaml(model_name)
|
49
|
+
end
|
50
|
+
fixtures[model_name.to_sym].keys.map{|name| use(:"#{model_name}__#{name}")}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
ret.length == 1 ? ret[0] : ret
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
require 'fixture_dependencies/active_record' if defined?(ActiveRecord::Base)
|
58
|
+
require 'fixture_dependencies/sequel' if defined?(Sequel::Model)
|
59
|
+
require 'erb'
|
60
|
+
|
61
|
+
class << FixtureDependencies
|
62
|
+
attr_reader :fixtures, :loaded
|
63
|
+
attr_accessor :verbose, :fixture_path
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Add a fixture to the fixture hash (does not add to the database,
|
68
|
+
# just makes it available to be add to the database via use).
|
69
|
+
def add(model_name, name, attributes)
|
70
|
+
(fixtures[model_name.to_sym]||={})[name.to_sym] = attributes
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the model instance that already exists in the database using
|
74
|
+
# the fixture name.
|
75
|
+
def get(record)
|
76
|
+
model_name, name = split_name(record)
|
77
|
+
model = model_name.camelize.constantize
|
78
|
+
model_method(:model_find, model_type(model), model, fixtures[model_name.to_sym][name.to_sym][model.primary_key.to_sym])
|
79
|
+
end
|
80
|
+
|
81
|
+
# Adds all fixtures in the yaml fixture file for the model to the fixtures
|
82
|
+
# hash (does not add them to the database, see add).
|
83
|
+
def load_yaml(model_name)
|
84
|
+
raise(ArgumentError, "No fixture_path set. Use FixtureDependencies.fixture_path = ...") unless fixture_path
|
85
|
+
YAML.load(erb_render(File.read(File.join(fixture_path, "#{model_name.camelize.constantize.table_name}.yml")))).each do |name, attributes|
|
86
|
+
symbol_attrs = {}
|
87
|
+
attributes.each{|k,v| symbol_attrs[k.to_sym] = v}
|
88
|
+
add(model_name.to_sym, name, symbol_attrs)
|
89
|
+
end
|
90
|
+
loaded[model_name.to_sym] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delegate to the correct method based on mtype
|
94
|
+
def model_method(meth, mtype, *args, &block)
|
95
|
+
send("#{meth}_#{mtype}", *args, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
# A symbol representing the base class of the model, currently
|
99
|
+
# ActiveRecord::Base and Sequel::Model are supported.
|
100
|
+
def model_type(model)
|
101
|
+
if model.ancestors.map{|x| x.to_s}.include?('ActiveRecord::Base')
|
102
|
+
:AR
|
103
|
+
elsif model.ancestors.map{|x| x.to_s}.include?('Sequel::Model')
|
104
|
+
:S
|
105
|
+
else
|
106
|
+
raise TypeError, 'not ActiveRecord or Sequel model'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Split the fixture name into the name of the model and the name of
|
111
|
+
# the individual fixture.
|
112
|
+
def split_name(name)
|
113
|
+
name.to_s.split('__', 2)
|
114
|
+
end
|
115
|
+
|
116
|
+
# run the loaded fixture through ERB before loading it.
|
117
|
+
def erb_render(fixture_content)
|
118
|
+
ERB.new(fixture_content).result
|
119
|
+
end
|
120
|
+
|
121
|
+
# Load the individual fixture into the database, by loading all necessary
|
122
|
+
# belongs_to dependencies before saving the model, and all has_*
|
123
|
+
# dependencies after saving the model. If the model already exists in
|
124
|
+
# the database, return it. Will check the yaml file for fixtures if no
|
125
|
+
# fixtures yet exist for the model. If the fixture isn't in the fixture
|
126
|
+
# hash, raise an error.
|
127
|
+
def use(record, loading = [], procs = {})
|
128
|
+
spaces = " " * loading.length
|
129
|
+
puts "#{spaces}using #{record}" if verbose > 0
|
130
|
+
puts "#{spaces}load stack:#{loading.inspect}" if verbose > 1
|
131
|
+
loading.push(record)
|
132
|
+
model_name, name = split_name(record)
|
133
|
+
model = model_name.camelize.constantize
|
134
|
+
unless loaded[model_name.to_sym]
|
135
|
+
puts "#{spaces}loading #{model.table_name}.yml" if verbose > 0
|
136
|
+
load_yaml(model_name)
|
137
|
+
end
|
138
|
+
mtype = model_type(model)
|
139
|
+
model_method(:raise_model_error, mtype, "Couldn't use fixture #{record.inspect}") unless attributes = fixtures[model_name.to_sym][name.to_sym]
|
140
|
+
# return if object has already been loaded into the database
|
141
|
+
if existing_obj = model_method(:model_find_by_pk, mtype, model, attributes[model.primary_key.to_sym])
|
142
|
+
puts "#{spaces}using #{record}: already in database" if verbose > 2
|
143
|
+
loading.pop
|
144
|
+
return existing_obj
|
145
|
+
end
|
146
|
+
obj = model.new
|
147
|
+
many_associations = []
|
148
|
+
attributes.each do |attr, value|
|
149
|
+
if reflection = model_method(:reflection, mtype, model, attr.to_sym)
|
150
|
+
if [:belongs_to, :many_to_one].include?(model_method(:reflection_type, mtype, reflection))
|
151
|
+
dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
|
152
|
+
if dep_name == record
|
153
|
+
# Self referential record, use primary key
|
154
|
+
puts "#{spaces}#{record}.#{attr}: belongs_to self-referential" if verbose > 1
|
155
|
+
attr = model_method(:reflection_key, mtype, reflection)
|
156
|
+
value = attributes[model.primary_key.to_sym]
|
157
|
+
elsif loading.include?(dep_name)
|
158
|
+
# Association cycle detected, set foreign key for this model afterward using procs
|
159
|
+
# This is will fail if the column is set to not null or validates_presence_of
|
160
|
+
puts "#{spaces}#{record}.#{attr}: belongs-to cycle detected:#{dep_name}" if verbose > 1
|
161
|
+
(procs[dep_name] ||= []) << Proc.new do |assoc|
|
162
|
+
m = model_method(:model_find, mtype, model, attributes[model.primary_key.to_sym])
|
163
|
+
m.send("#{attr}=", assoc)
|
164
|
+
model_method(:model_save, mtype, m)
|
165
|
+
end
|
166
|
+
value = nil
|
167
|
+
else
|
168
|
+
# Regular assocation, load it
|
169
|
+
puts "#{spaces}#{record}.#{attr}: belongs_to:#{dep_name}" if verbose > 1
|
170
|
+
use(dep_name, loading, procs)
|
171
|
+
value = get(dep_name)
|
172
|
+
end
|
173
|
+
elsif
|
174
|
+
many_associations << [attr, reflection, model_method(:reflection_type, mtype, reflection) == :has_one ? [value] : value]
|
175
|
+
next
|
176
|
+
end
|
177
|
+
end
|
178
|
+
puts "#{spaces}#{record}.#{attr} = #{value.inspect}" if verbose > 2
|
179
|
+
obj.send("#{attr}=", value)
|
180
|
+
end
|
181
|
+
|
182
|
+
puts "#{spaces}saving #{record}" if verbose > 1
|
183
|
+
|
184
|
+
model_method(:model_save, mtype, obj)
|
185
|
+
# after saving the model, we set the primary key within the fixture hash, in case it was not explicitly specified in the fixture and was generated by an auto_increment / serial field
|
186
|
+
fixtures[model_name.to_sym][name.to_sym][model.primary_key.to_sym] ||= obj[model.primary_key.to_sym]
|
187
|
+
|
188
|
+
loading.pop
|
189
|
+
# Update the circular references
|
190
|
+
if procs[record]
|
191
|
+
procs[record].each{|p| p.call(obj)}
|
192
|
+
procs.delete(record)
|
193
|
+
end
|
194
|
+
# Update the has_many and habtm associations
|
195
|
+
many_associations.each do |attr, reflection, values|
|
196
|
+
values.each do |value|
|
197
|
+
dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
|
198
|
+
rtype = model_method(:reflection_type, mtype, reflection) if verbose > 1
|
199
|
+
if dep_name == record
|
200
|
+
# Self referential, add association
|
201
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype} self-referential" if verbose > 1
|
202
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, obj)
|
203
|
+
elsif loading.include?(dep_name)
|
204
|
+
# Cycle Detected, add association to this object after saving other object
|
205
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype} cycle detected:#{dep_name}" if verbose > 1
|
206
|
+
(procs[dep_name] ||= []) << Proc.new do |assoc|
|
207
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, assoc)
|
208
|
+
end
|
209
|
+
else
|
210
|
+
# Regular association, add it
|
211
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype}:#{dep_name}" if verbose > 1
|
212
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, use(dep_name, loading, procs))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
obj
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class << FixtureDependencies
|
2
|
+
private
|
3
|
+
|
4
|
+
def add_associated_object_AR(reflection, attr, object, assoc)
|
5
|
+
if reflection.macro == :has_one
|
6
|
+
object.send("#{attr}=", assoc)
|
7
|
+
elsif !object.send(attr).include?(assoc)
|
8
|
+
object.send(attr) << assoc
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_find_AR(model, pk)
|
13
|
+
model.find(pk)
|
14
|
+
end
|
15
|
+
|
16
|
+
def model_find_by_pk_AR(model, pk)
|
17
|
+
model.send("find_by_#{model.primary_key}", pk)
|
18
|
+
end
|
19
|
+
|
20
|
+
def model_save_AR(object)
|
21
|
+
object.save || raise(ActiveRecord::ActiveRecordError)
|
22
|
+
end
|
23
|
+
|
24
|
+
def raise_model_error_AR(message)
|
25
|
+
ActiveRecord::RecordNotFound
|
26
|
+
end
|
27
|
+
|
28
|
+
def reflection_AR(model, attr)
|
29
|
+
model.reflect_on_association(attr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def reflection_class_AR(reflection)
|
33
|
+
reflection.klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def reflection_key_AR(reflection)
|
37
|
+
reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def reflection_type_AR(reflection)
|
41
|
+
reflection.macro
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Spec::Example::ExampleGroup
|
2
|
+
def execute(*args, &block)
|
3
|
+
begin
|
4
|
+
Sequel::Model.db.transaction{super(*args, &block); raise Sequel::Error::Rollback}
|
5
|
+
return true
|
6
|
+
rescue
|
7
|
+
return false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(*args)
|
12
|
+
FixtureDependencies.load(*args)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class << FixtureDependencies
|
2
|
+
private
|
3
|
+
|
4
|
+
def add_associated_object_S(reflection, attr, object, assoc)
|
5
|
+
object.send("add_#{attr.to_s.singularize}", assoc) unless object.send(attr).include?(assoc)
|
6
|
+
end
|
7
|
+
|
8
|
+
def model_find_S(model, pk)
|
9
|
+
model[pk] || raise(Sequel::Error)
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_find_by_pk_S(model, pk)
|
13
|
+
model[pk]
|
14
|
+
end
|
15
|
+
|
16
|
+
def model_save_S(object)
|
17
|
+
object.raise_on_save_failure = true
|
18
|
+
object.save
|
19
|
+
end
|
20
|
+
|
21
|
+
def raise_model_error_S(message)
|
22
|
+
Sequel::Error
|
23
|
+
end
|
24
|
+
|
25
|
+
def reflection_S(model, attr)
|
26
|
+
model.association_reflection(attr)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reflection_class_S(reflection)
|
30
|
+
reflection.associated_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def reflection_key_S(reflection)
|
34
|
+
reflection[:key]
|
35
|
+
end
|
36
|
+
|
37
|
+
def reflection_type_S(reflection)
|
38
|
+
reflection[:type]
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'fixture_dependencies/test_unit'
|
2
|
+
FixtureDependencies.fixture_path = Test::Unit::TestCase.fixture_path
|
3
|
+
|
4
|
+
module Test
|
5
|
+
module Unit
|
6
|
+
class TestCase
|
7
|
+
class << self
|
8
|
+
alias_method :stupid_method_added, :method_added
|
9
|
+
end
|
10
|
+
def self.method_added(x)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Load fixtures using FixtureDependencies inside a transaction
|
14
|
+
def setup_with_fixtures
|
15
|
+
ActiveRecord::Base.send :increment_open_transactions
|
16
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
17
|
+
load_fixtures
|
18
|
+
end
|
19
|
+
alias_method :setup, :setup_with_fixtures
|
20
|
+
|
21
|
+
class << self
|
22
|
+
alias_method :method_added, :stupid_method_added
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Load fixtures named with the fixtures class method
|
28
|
+
def load_fixtures
|
29
|
+
load(*fixture_table_names)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'fixture_dependencies/test_unit'
|
2
|
+
|
3
|
+
class FixtureDependencies::SequelTestCase < Test::Unit::TestCase
|
4
|
+
# Work around for Rails stupidity
|
5
|
+
undef_method :default_test if method_defined?(:default_test)
|
6
|
+
|
7
|
+
def run(*args, &block)
|
8
|
+
Sequel::Model.db.transaction do
|
9
|
+
super
|
10
|
+
raise Sequel::Error::Rollback
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fixture_dependencies/test_unit/rails'
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smulube-fixture_dependencies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
- Sam Mulube
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-05-16 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: "fixture_dependencies is an advanced fixture loader for ActiveRecord and Sequel, allowing the loading of models from YAML fixtures, along with their entire dependency graph. It has the following features: - Fixtures specify association names instead of foreign keys - Support both Sequel and ActiveRecord - Supports many_to_one/belongs_to, one_to_many/has_many, many_to_many/has_and_belongs_to_many, and has_one associations - Loads a fixture's dependency graph in such a manner that foreign key constraints aren't violated - Has a very simple API (FixtureDependencies.load(:model__fixture)) - Handles almost all cyclic dependencies - Includes Rails and Sequel test helpers for Test::Unit (and a Sequel test helper for RSpec) that load fixtures for every test inside a transaction, so fixture data is never left in your database - Adds dynamic fixtures (similar to ActiveRecord) with ERb."
|
18
|
+
email:
|
19
|
+
- code@jeremyevans.net
|
20
|
+
- sam@mulube.com
|
21
|
+
executables: []
|
22
|
+
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files:
|
26
|
+
- LICENSE
|
27
|
+
files:
|
28
|
+
- README
|
29
|
+
- LICENSE
|
30
|
+
- lib/fixture_dependencies.rb
|
31
|
+
- lib/fixture_dependencies_test_help.rb
|
32
|
+
- lib/fixture_dependencies/sequel.rb
|
33
|
+
- lib/fixture_dependencies/active_record.rb
|
34
|
+
- lib/fixture_dependencies/test_unit.rb
|
35
|
+
- lib/fixture_dependencies/test_unit/rails.rb
|
36
|
+
- lib/fixture_dependencies/test_unit/sequel.rb
|
37
|
+
- lib/fixture_dependencies/rspec/sequel.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage:
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --inline-source
|
43
|
+
- --line-numbers
|
44
|
+
- README
|
45
|
+
- lib
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: Sequel/ActiveRecord fixture loader that handles dependency graphs. Supports dynamic ERb fixtures.
|
67
|
+
test_files: []
|
68
|
+
|