fixture_dependencies 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README +254 -0
- data/lib/fixture_dependencies/active_record.rb +43 -0
- data/lib/fixture_dependencies/rspec/sequel.rb +9 -0
- data/lib/fixture_dependencies/sequel.rb +40 -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_unit.rb +14 -0
- data/lib/fixture_dependencies.rb +213 -0
- data/lib/fixture_dependencies_test_help.rb +1 -0
- metadata +65 -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,254 @@
|
|
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
|
+
|
19
|
+
== Installation
|
20
|
+
|
21
|
+
sudo gem install jeremyevans-fixture_dependencies \
|
22
|
+
--source http://gems.github.com
|
23
|
+
|
24
|
+
== Source
|
25
|
+
|
26
|
+
Source is available via github:
|
27
|
+
|
28
|
+
http://github.com/jeremyevans/fixture_dependencies
|
29
|
+
|
30
|
+
You can check it out with git:
|
31
|
+
|
32
|
+
git clone git://github.com/jeremyevans/fixture_dependencies.git
|
33
|
+
|
34
|
+
== Usage
|
35
|
+
|
36
|
+
=== With Rails/ActiveRecord/Test::Unit:
|
37
|
+
|
38
|
+
Add the following to test/test_helper.rb after "require 'test_help'":
|
39
|
+
|
40
|
+
require 'fixture_dependencies/test_unit/rails'
|
41
|
+
|
42
|
+
This overrides the default test helper to load the fixtures inside transactions
|
43
|
+
and to use FixtureDependencies to load the fixtures.
|
44
|
+
|
45
|
+
=== With Sequel/Test::Unit:
|
46
|
+
|
47
|
+
Somewhere before the test code is loaded:
|
48
|
+
|
49
|
+
require 'fixture_dependencies/test_unit/sequel'
|
50
|
+
|
51
|
+
Make sure the test case classes use FixtureDependencies::SequelTestCase:
|
52
|
+
|
53
|
+
class ModelTest < FixtureDependencies::SequelTestCase
|
54
|
+
|
55
|
+
This runs the test cases inside a Sequel transaction.
|
56
|
+
|
57
|
+
=== With Sequel/RSpec:
|
58
|
+
|
59
|
+
Somewhere before the test code is loaded:
|
60
|
+
|
61
|
+
require 'fixture_dependencies/rspec/sequel'
|
62
|
+
|
63
|
+
This runs each spec inside a separate Sequel transaction.
|
64
|
+
|
65
|
+
=== With other testing libraries:
|
66
|
+
|
67
|
+
You can just use FixtureDependencies.load to handle the loading of fixtures.
|
68
|
+
The use of transactions is up to you. One thing you must do if you are
|
69
|
+
not using the rails test helper is to set the fixture path for
|
70
|
+
FixtureDependencies:
|
71
|
+
|
72
|
+
FixtureDependencies.fixture_path = '/path/to/fixtures'
|
73
|
+
|
74
|
+
== Changes to Rails default fixtures:
|
75
|
+
|
76
|
+
fixture_dependencies is designed to require the least possible changes to
|
77
|
+
the default YAML fixtures used by Rails (well, at least Rails 1.2 and earlier).
|
78
|
+
For example, see the following changes:
|
79
|
+
|
80
|
+
OLD NEW
|
81
|
+
asset1: asset1:
|
82
|
+
id: 1 id: 1
|
83
|
+
employee_id: 2 employee: jeremy
|
84
|
+
product_id: 3 product: nx7010
|
85
|
+
vendor_id: 2 vendor: lxg_computers
|
86
|
+
note: in working order note: in working order
|
87
|
+
|
88
|
+
As you can see, you just replace the foreign key attribute and value with the
|
89
|
+
name of the association and the associations name. This assumes you have an
|
90
|
+
employee fixture with a name of jeremy, and products fixture with the name of
|
91
|
+
nx7010, and a vendors fixture with the name lxg_computers.
|
92
|
+
|
93
|
+
Fixture files still use the table_name of the model.
|
94
|
+
|
95
|
+
== Changes to the fixtures Class Method:
|
96
|
+
|
97
|
+
fixture_dependencies can still use the fixtures class method in your test:
|
98
|
+
|
99
|
+
class EmployeeTest < Test::Unit::TestCase
|
100
|
+
fixtures :assets
|
101
|
+
end
|
102
|
+
|
103
|
+
In Rails default testing practices, the arguments to fixtures are table names.
|
104
|
+
fixture_dependencies changes this to underscored model names. If you are using
|
105
|
+
Rails' recommended table practices, this shouldn't make a difference.
|
106
|
+
|
107
|
+
It is recommended that you do not use the fixtures method, and instead load
|
108
|
+
individual fixtures as needed (see below). This makes your tests much more
|
109
|
+
robust, in case you want to add or remove individual fixtures at a later date.
|
110
|
+
|
111
|
+
== Loading individual fixtures with fixtures class method
|
112
|
+
|
113
|
+
There is support for loading individual fixtures (and just their dependencies),
|
114
|
+
using the following syntax:
|
115
|
+
|
116
|
+
class EmployeeTest < Test::Unit::TestCase
|
117
|
+
fixtures :employee__jeremy # Note the double underscore
|
118
|
+
end
|
119
|
+
|
120
|
+
This would load just the jeremy fixture and its dependencies. I find this is
|
121
|
+
much better than loading all fixtures in most of my test suites. Even better
|
122
|
+
is loading just the fixtures you want instead every test method (see below).
|
123
|
+
This leads to the most robust testing.
|
124
|
+
|
125
|
+
== Loading fixtures inside test methods
|
126
|
+
|
127
|
+
I find that it is often better to skip the use of the fixtures method entirely,
|
128
|
+
and load the fixtures I want manually in each test method. This provides for
|
129
|
+
the loosest coupling possible. Here's an example:
|
130
|
+
|
131
|
+
class EmployeeTest < Test::Unit::TestCase
|
132
|
+
def test_employee_name
|
133
|
+
# Load the fixture and return the Employee object
|
134
|
+
employee = load(:employee__jeremy)
|
135
|
+
# Test the employee
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_employees
|
139
|
+
# Load the fixtures and return two Employee objects
|
140
|
+
employee1, employee2 = load(:employees=>[:jeremy, :karl])
|
141
|
+
# Test the employees
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_award_statistics
|
145
|
+
# Load all fixtures in both tables
|
146
|
+
load(:employee_award__jeremy_first, :award__first)
|
147
|
+
# Test the award_statistics method
|
148
|
+
# (which pulls data from the tables loaded above)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Don't worry about loading the same fixture twice, if a fixture is already
|
153
|
+
loaded, it won't attempt to load it again.
|
154
|
+
|
155
|
+
== one_to_many/many_to_many/has_many/has_and_belongs_to_many assocations
|
156
|
+
|
157
|
+
Here's an example of using has_one (logon_information), has_many (assets), and
|
158
|
+
has_and_belongs_to_many (groups) associations.
|
159
|
+
|
160
|
+
jeremy:
|
161
|
+
id: 2
|
162
|
+
name: Jeremy Evans
|
163
|
+
logon_information: jeremy
|
164
|
+
assets: [asset1, asset2, asset3]
|
165
|
+
groups: [group1]
|
166
|
+
|
167
|
+
logon_information is a has_one association to another table which was split
|
168
|
+
from the employees table due to database security requirements. Assets is a
|
169
|
+
has_many association, where one employee is responsible for the asset.
|
170
|
+
Employees can be a member of multiple groups, and each group can have multiple
|
171
|
+
employees.
|
172
|
+
|
173
|
+
For has_* associations, after fixture_dependencies saves jeremy, it will load
|
174
|
+
and save logon_information (and its dependencies...), it will load each asset
|
175
|
+
in the order specified (and their dependencies...), and it will load all of the
|
176
|
+
groups in the order specified (and their dependencies...). Note that there
|
177
|
+
is only a load order inside a specific association, associations are stored
|
178
|
+
in the same hash as attributes and are loaded in an arbitrary order.
|
179
|
+
|
180
|
+
== many_to_many/has_and_belongs_to_many join table fixtures
|
181
|
+
|
182
|
+
Another change is that Rails defaults allow you to specify habtm join tables in
|
183
|
+
fixtures. That doesn't work with fixture dependencies, as there is no
|
184
|
+
associated model. Instead, you use a has_and_belongs_to_many association name
|
185
|
+
in the the appropriate model fixtures (see above).
|
186
|
+
|
187
|
+
== Cyclic dependencies
|
188
|
+
|
189
|
+
fixture_dependencies handles almost all cyclic dependencies. It handles all
|
190
|
+
has_many, has_one, and habtm cyclic dependencies. It handles all
|
191
|
+
self-referential cyclic dependencies. It handles all belongs_to cyclic
|
192
|
+
dependencies except the case where there is a NOT NULL or validates_presence of
|
193
|
+
constraint on the cyclic dependency's foreign key.
|
194
|
+
|
195
|
+
For example, a case that won't work is when employee belongs_to supervisor
|
196
|
+
(with a NOT NULL or validates_presence_of constraint on supervisor_id), and
|
197
|
+
john is karl's supervisor and karl is john's supervisor. Since you can't create
|
198
|
+
john without a valid supervisor_id, you need to create karl first, but you
|
199
|
+
can't create karl for the same reason (as john doesn't exist yet).
|
200
|
+
|
201
|
+
There isn't a generic way to handle the belongs_to cyclic dependency, as far as
|
202
|
+
I know. Deferring foreign key checks could work, but may not be enabled (and
|
203
|
+
one of the main reasons to use the plugin is that it doesn't require them).
|
204
|
+
For associations like the example above (employee's supervisor is also an
|
205
|
+
employee), setting the foreign_key to the primary key and then changing it
|
206
|
+
later is an option, but database checks may prevent it. For more complex
|
207
|
+
cyclic dependencies involving multiple model classes (employee belongs_to
|
208
|
+
division belongs_to head_of_division when the employee is a member of the
|
209
|
+
division and also the head of the division), even that approach is not
|
210
|
+
possible.
|
211
|
+
|
212
|
+
== Known issues
|
213
|
+
|
214
|
+
Currently, the plugin only supports YAML fixtures, but other types of fixtures
|
215
|
+
would be fairly easy to add (send me a patch if you add support for another
|
216
|
+
fixture type).
|
217
|
+
|
218
|
+
The plugin is significantly slower than the default testing method, because it
|
219
|
+
loads all fixtures inside of a transaction (one per test method), where Rails
|
220
|
+
defaults to loading the fixtures once per test suite (outside of a
|
221
|
+
transaction), and only deletes fixtures from a table when overwriting it with
|
222
|
+
new fixtures.
|
223
|
+
|
224
|
+
Instantiated fixtures are not available with this plugin. Instead, you should
|
225
|
+
use load(:model__fixture_name).
|
226
|
+
|
227
|
+
== Troubleshooting
|
228
|
+
|
229
|
+
If you run into problems with loading your fixtures, it can be difficult to see
|
230
|
+
where the problems are. To aid in debugging an error, add the following to
|
231
|
+
test/test_helper.rb:
|
232
|
+
|
233
|
+
FixtureDependencies.verbose = 3
|
234
|
+
|
235
|
+
This will give a verbose description of the loading and saving of fixtures for
|
236
|
+
every test, including the recursive loading of the dependency graph.
|
237
|
+
|
238
|
+
== Similar Ideas
|
239
|
+
|
240
|
+
Rails now supports something similar by default. Honestly, I'm not sure what
|
241
|
+
the differences are.
|
242
|
+
|
243
|
+
fixture_references is a similar plugin. It uses erb inside yaml, and uses the
|
244
|
+
foreign key numbers inside of the association names, which leads me to believe
|
245
|
+
it doesn't support has_* associations.
|
246
|
+
|
247
|
+
== License
|
248
|
+
|
249
|
+
fixture_dependencies is released under the MIT License. See the LICENSE file
|
250
|
+
for details.
|
251
|
+
|
252
|
+
== Author
|
253
|
+
|
254
|
+
Jeremy Evans <code@jeremyevans.net>
|
@@ -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,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,213 @@
|
|
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
|
+
|
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(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
|
+
# Load the individual fixture into the database, by loading all necessary
|
117
|
+
# belongs_to dependencies before saving the model, and all has_*
|
118
|
+
# dependencies after saving the model. If the model already exists in
|
119
|
+
# the database, return it. Will check the yaml file for fixtures if no
|
120
|
+
# fixtures yet exist for the model. If the fixture isn't in the fixture
|
121
|
+
# hash, raise an error.
|
122
|
+
def use(record, loading = [], procs = {})
|
123
|
+
spaces = " " * loading.length
|
124
|
+
puts "#{spaces}using #{record}" if verbose > 0
|
125
|
+
puts "#{spaces}load stack:#{loading.inspect}" if verbose > 1
|
126
|
+
loading.push(record)
|
127
|
+
model_name, name = split_name(record)
|
128
|
+
model = model_name.camelize.constantize
|
129
|
+
unless loaded[model_name.to_sym]
|
130
|
+
puts "#{spaces}loading #{model.table_name}.yml" if verbose > 0
|
131
|
+
load_yaml(model_name)
|
132
|
+
end
|
133
|
+
mtype = model_type(model)
|
134
|
+
model_method(:raise_model_error, mtype, "Couldn't use fixture #{record.inspect}") unless attributes = fixtures[model_name.to_sym][name.to_sym]
|
135
|
+
# return if object has already been loaded into the database
|
136
|
+
if existing_obj = model_method(:model_find_by_pk, mtype, model, attributes[model.primary_key.to_sym])
|
137
|
+
puts "#{spaces}using #{record}: already in database" if verbose > 2
|
138
|
+
loading.pop
|
139
|
+
return existing_obj
|
140
|
+
end
|
141
|
+
obj = model.new
|
142
|
+
many_associations = []
|
143
|
+
attributes.each do |attr, value|
|
144
|
+
if reflection = model_method(:reflection, mtype, model, attr.to_sym)
|
145
|
+
if [:belongs_to, :many_to_one].include?(model_method(:reflection_type, mtype, reflection))
|
146
|
+
dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
|
147
|
+
if dep_name == record
|
148
|
+
# Self referential record, use primary key
|
149
|
+
puts "#{spaces}#{record}.#{attr}: belongs_to self-referential" if verbose > 1
|
150
|
+
attr = model_method(:reflection_key, mtype, reflection)
|
151
|
+
value = attributes[model.primary_key.to_sym]
|
152
|
+
elsif loading.include?(dep_name)
|
153
|
+
# Association cycle detected, set foreign key for this model afterward using procs
|
154
|
+
# This is will fail if the column is set to not null or validates_presence_of
|
155
|
+
puts "#{spaces}#{record}.#{attr}: belongs-to cycle detected:#{dep_name}" if verbose > 1
|
156
|
+
(procs[dep_name] ||= []) << Proc.new do |assoc|
|
157
|
+
m = model_method(:model_find, mtype, model, attributes[model.primary_key.to_sym])
|
158
|
+
m.send("#{attr}=", assoc)
|
159
|
+
model_method(:model_save, mtype, m)
|
160
|
+
end
|
161
|
+
value = nil
|
162
|
+
else
|
163
|
+
# Regular assocation, load it
|
164
|
+
puts "#{spaces}#{record}.#{attr}: belongs_to:#{dep_name}" if verbose > 1
|
165
|
+
use(dep_name, loading, procs)
|
166
|
+
value = get(dep_name)
|
167
|
+
end
|
168
|
+
elsif
|
169
|
+
many_associations << [attr, reflection, model_method(:reflection_type, mtype, reflection) == :has_one ? [value] : value]
|
170
|
+
next
|
171
|
+
end
|
172
|
+
end
|
173
|
+
puts "#{spaces}#{record}.#{attr} = #{value.inspect}" if verbose > 2
|
174
|
+
obj.send("#{attr}=", value)
|
175
|
+
end
|
176
|
+
|
177
|
+
puts "#{spaces}saving #{record}" if verbose > 1
|
178
|
+
|
179
|
+
model_method(:model_save, mtype, obj)
|
180
|
+
# 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
|
181
|
+
fixtures[model_name.to_sym][name.to_sym][model.primary_key.to_sym] ||= obj[model.primary_key.to_sym]
|
182
|
+
|
183
|
+
loading.pop
|
184
|
+
# Update the circular references
|
185
|
+
if procs[record]
|
186
|
+
procs[record].each{|p| p.call(obj)}
|
187
|
+
procs.delete(record)
|
188
|
+
end
|
189
|
+
# Update the has_many and habtm associations
|
190
|
+
many_associations.each do |attr, reflection, values|
|
191
|
+
values.each do |value|
|
192
|
+
dep_name = "#{model_method(:reflection_class, mtype, reflection).name.underscore}__#{value}".to_sym
|
193
|
+
rtype = model_method(:reflection_type, mtype, reflection) if verbose > 1
|
194
|
+
if dep_name == record
|
195
|
+
# Self referential, add association
|
196
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype} self-referential" if verbose > 1
|
197
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, obj)
|
198
|
+
elsif loading.include?(dep_name)
|
199
|
+
# Cycle Detected, add association to this object after saving other object
|
200
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype} cycle detected:#{dep_name}" if verbose > 1
|
201
|
+
(procs[dep_name] ||= []) << Proc.new do |assoc|
|
202
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, assoc)
|
203
|
+
end
|
204
|
+
else
|
205
|
+
# Regular association, add it
|
206
|
+
puts "#{spaces}#{record}.#{attr}: #{rtype}:#{dep_name}" if verbose > 1
|
207
|
+
model_method(:add_associated_object, mtype, reflection, attr, obj, use(dep_name, loading, procs))
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
obj
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fixture_dependencies/test_unit/rails'
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixture_dependencies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-06 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: code@jeremyevans.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- LICENSE
|
27
|
+
- lib/fixture_dependencies.rb
|
28
|
+
- lib/fixture_dependencies_test_help.rb
|
29
|
+
- lib/fixture_dependencies/sequel.rb
|
30
|
+
- lib/fixture_dependencies/active_record.rb
|
31
|
+
- lib/fixture_dependencies/test_unit.rb
|
32
|
+
- lib/fixture_dependencies/test_unit/rails.rb
|
33
|
+
- lib/fixture_dependencies/test_unit/sequel.rb
|
34
|
+
- lib/fixture_dependencies/rspec/sequel.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage:
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options:
|
39
|
+
- --inline-source
|
40
|
+
- --line-numbers
|
41
|
+
- README
|
42
|
+
- lib
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Sequel/ActiveRecord fixture loader that handles dependency graphs
|
64
|
+
test_files: []
|
65
|
+
|