mongoid_rails_fixtures 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .bundle
2
+ .DS_Store
3
+ .rvmrc
4
+ .gem
5
+ log/
6
+ .log
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # Use `bundle install` in order to install these gems
2
+ # Use `bundle exec rake` in order to run the specs using the bundle
3
+ source :rubygems
4
+
5
+ gem "rails", ">=3.0.0.rc"
6
+ gem "railties", ">=3.0.0.rc"
7
+ gem "activesupport", ">=3.0.0.rc"
8
+ gem "mongoid", ">=2.0.0.beta.7"
9
+ gem "mongoid_rails_migrations", ">=0.0.4"
data/README.rdoc ADDED
@@ -0,0 +1,61 @@
1
+ == SYNOPSIS
2
+ * A case of the fixtures for the mongoid
3
+
4
+ == WHY?
5
+ * Fixtures can be kinda nice for classic rails workflow
6
+ * Some of us don't like factories
7
+ * Factories can't test db server side code, e.g. Mongodb server.js
8
+
9
+ == REQUIREMENTS
10
+ * rails >= 3.0.0.rc
11
+ * mongoid >= 2.0.0.beta.7
12
+
13
+ == INSTALL
14
+ * gem install mongoid_rails_fixtures
15
+ * In your Gemfile, include:
16
+ gem "mongoid_rails_fixtures", ">=0.0.1" # or current version
17
+
18
+ == OPTIONS
19
+ to use, set these attributes after the Bunlder.require in your application.rb
20
+ * to migrate before test runs, optionally include
21
+ +typewriter+Mongoid.configure.migrate_before_tests = true+typewriter+
22
+ * to stop the deletion of existing fixtures before create (a rails default), set
23
+ +typewriter+Mongoid.configure.delete_existing_fixtures_before_create = false+typewriter+
24
+
25
+ == FEATURES AND HOW TO USE
26
+ * you can simply create your own fixture files
27
+ * I've been thinking about creating a generator but it seems to be overkill to automate the process
28
+ * place your fixtures as pluralized models either in .yml or .csv (e.g. users.yml) in your rails/test/fixtures directory
29
+ * execute with a standard call to
30
+ +typewriter+rake test:units+typewriter+
31
+
32
+ == NOTES
33
+ * Original active_record/fixtures.rb is unchanged for association detection (so that likely doesn't work)
34
+ * Only tested with ruby 1.9.1
35
+
36
+ == TODO
37
+ * make association detection functional in fixtures.rb
38
+ *
39
+
40
+ == CREDITS TO
41
+ * rails
42
+ * mongoid
43
+
44
+ Much of this gem simply modifies existing code from both projects.
45
+ With that out of the way, on to the license.
46
+
47
+ == LICENSE (MIT)
48
+
49
+ Copyright © 2010: Alan Da Costa
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'),
52
+ to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
57
+
58
+ The software is provided 'as is', without warranty of any kind, express or implied, including but not limited to the warranties of
59
+ merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any
60
+ claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
61
+ software or the use or other dealings in the software.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ namespace :test do
2
+ namespace :mongoid do
3
+ desc "Test mongoid rails migrations"
4
+ task :fixtures do
5
+ require File.dirname(__FILE__) + '/test/fixture_test'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module MongoidRailsFixtures
3
+ def self.dirname; File.dirname(__FILE__); end
4
+ end
5
+
6
+ require 'rubygems'
7
+ # Set up gems listed in the Gemfile.
8
+ if File.exist?("#{MongoidRailsFixtures.dirname}/../../Gemfile")
9
+ require 'bundler'
10
+ Bundler.setup
11
+ end
12
+
13
+ Bundler.require(:default) if defined?(Bundler)
14
+
15
+ # action_controller is bundled inside rails, we can't gem require
16
+ require 'action_controller'
17
+
18
+ # Add base to path incase not included as a gem
19
+ $:.unshift(MongoidRailsFixtures.dirname) unless
20
+ $:.include?(MongoidRailsFixtures.dirname) || $:.include?(File.expand_path(MongoidRailsFixtures.dirname))
21
+
22
+ %w[ timestamps_ext railtie_ext fixtures test_help mongoid_ext ].each do |file|
23
+ require "#{MongoidRailsFixtures.dirname}/mongoid_rails_fixtures/mongoid_ext/#{file}"
24
+ end
@@ -0,0 +1,982 @@
1
+ # extensions for compatibility with existing rails setup
2
+ module Mongoid
3
+ class << self
4
+ # Silence deprecation warnings within the block.
5
+ def silence
6
+ old_silenced, @silenced = @silenced, true
7
+ yield
8
+ ensure
9
+ @silenced = old_silenced
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'erb'
15
+ require 'yaml'
16
+ require 'csv'
17
+ require 'zlib'
18
+ require 'active_support/dependencies'
19
+ require 'active_support/core_ext/array/wrap'
20
+ require 'active_support/core_ext/object/blank'
21
+ require 'active_support/core_ext/logger'
22
+ require 'active_support/core_ext/class/delegating_attributes'
23
+
24
+ if RUBY_VERSION < '1.9'
25
+ module YAML #:nodoc:
26
+ class Omap #:nodoc:
27
+ def keys; map { |k, v| k } end
28
+ def values; map { |k, v| v } end
29
+ end
30
+ end
31
+ end
32
+
33
+ class FixtureClassNotFound < StandardError #:nodoc:
34
+ end
35
+
36
+ # Fixtures are a way of organizing data that you want to test against; in short, sample data.
37
+ #
38
+ # = Fixture formats
39
+ #
40
+ # Fixtures come in 3 flavors:
41
+ #
42
+ # 1. YAML fixtures
43
+ # 2. CSV fixtures
44
+ # 3. Single-file fixtures
45
+ #
46
+ # == YAML fixtures
47
+ #
48
+ # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
49
+ # in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
50
+ #
51
+ # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
52
+ # by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
53
+ # put your files in <tt><your-rails-app>/test/fixtures/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
54
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
55
+ #
56
+ # rubyonrails:
57
+ # id: 1
58
+ # name: Ruby on Rails
59
+ # url: http://www.rubyonrails.org
60
+ #
61
+ # google:
62
+ # id: 2
63
+ # name: Google
64
+ # url: http://www.google.com
65
+ #
66
+ # This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
67
+ # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
68
+ # pleasure.
69
+ #
70
+ # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
71
+ # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
72
+ # This is commonly needed for tree structures. Example:
73
+ #
74
+ # --- !omap
75
+ # - parent:
76
+ # id: 1
77
+ # parent_id: NULL
78
+ # title: Parent
79
+ # - child:
80
+ # id: 2
81
+ # parent_id: 1
82
+ # title: Child
83
+ #
84
+ # == CSV fixtures
85
+ #
86
+ # Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored
87
+ # in a single file, but instead end with the <tt>.csv</tt> file extension
88
+ # (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
89
+ #
90
+ # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
91
+ # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
92
+ # of the actual data (1 per line). Here's an example:
93
+ #
94
+ # id, name, url
95
+ # 1, Ruby On Rails, http://www.rubyonrails.org
96
+ # 2, Google, http://www.google.com
97
+ #
98
+ # Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
99
+ # need to use a double quote character, you must escape it with another double quote.
100
+ #
101
+ # Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
102
+ # fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
103
+ # number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
104
+ # "web_site_2".
105
+ #
106
+ # Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
107
+ # have existing data somewhere already.
108
+ #
109
+ # == Single-file fixtures
110
+ #
111
+ # This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
112
+ # Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
113
+ # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
114
+ # put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
115
+ # like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
116
+ #
117
+ # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
118
+ # extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
119
+ # above example might look like:
120
+ #
121
+ # web_sites/google
122
+ # web_sites/yahoo.txt
123
+ # web_sites/ruby-on-rails
124
+ #
125
+ # The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
126
+ # of "name => value". Here's an example of the ruby-on-rails fixture above:
127
+ #
128
+ # id => 1
129
+ # name => Ruby on Rails
130
+ # url => http://www.rubyonrails.org
131
+ #
132
+ # = Using fixtures in testcases
133
+ #
134
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
135
+ # fixtures, but first let's take a look at a sample unit test:
136
+ #
137
+ # require 'test_helper'
138
+ #
139
+ # class WebSiteTest < ActiveSupport::TestCase
140
+ # test "web_site_count" do
141
+ # assert_equal 2, WebSite.count
142
+ # end
143
+ # end
144
+ #
145
+ # By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database, so this test will succeed.
146
+ # The testing environment will automatically load the all fixtures into the database before each test.
147
+ # To ensure consistent data, the environment deletes the fixtures before running the load.
148
+ #
149
+ # In addition to being available in the database, the fixture's data may also be accessed by
150
+ # using a special dynamic method, which has the same name as the model, and accepts the
151
+ # name of the fixture to instantiate:
152
+ #
153
+ # test "find" do
154
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
155
+ # end
156
+ #
157
+ # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the following tests:
158
+ #
159
+ # test "find_alt_method_1" do
160
+ # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
161
+ # end
162
+ #
163
+ # test "find_alt_method_2" do
164
+ # assert_equal "Ruby on Rails", @rubyonrails.news
165
+ # end
166
+ #
167
+ # In order to use these methods to access fixtured data within your testcases, you must specify one of the
168
+ # following in your <tt>ActiveSupport::TestCase</tt>-derived class:
169
+ #
170
+ # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
171
+ # self.use_instantiated_fixtures = true
172
+ #
173
+ # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
174
+ # self.use_instantiated_fixtures = :no_instances
175
+ #
176
+ # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
177
+ # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
178
+ # large sets of fixtured data.
179
+ #
180
+ # = Dynamic fixtures with ERb
181
+ #
182
+ # Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
183
+ # mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
184
+ #
185
+ # <% for i in 1..1000 %>
186
+ # fix_<%= i %>:
187
+ # id: <%= i %>
188
+ # name: guy_<%= 1 %>
189
+ # <% end %>
190
+ #
191
+ # This will create 1000 very simple YAML fixtures.
192
+ #
193
+ # Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
194
+ # This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
195
+ # sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
196
+ # is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
197
+ #
198
+ # = Transactional fixtures
199
+ #
200
+ # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
201
+ #
202
+ # class FooTest < ActiveSupport::TestCase
203
+ # self.use_transactional_fixtures = true
204
+ #
205
+ # test "godzilla" do
206
+ # assert !Foo.find(:all).empty?
207
+ # Foo.destroy_all
208
+ # assert Foo.find(:all).empty?
209
+ # end
210
+ #
211
+ # test "godzilla aftermath" do
212
+ # assert !Foo.find(:all).empty?
213
+ # end
214
+ # end
215
+ #
216
+ # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
217
+ # then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
218
+ #
219
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
220
+ # access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
221
+ #
222
+ # When *not* to use transactional fixtures:
223
+ #
224
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
225
+ # particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
226
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
227
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
228
+ # Use InnoDB, MaxDB, or NDB instead.
229
+ #
230
+ # = Advanced YAML Fixtures
231
+ #
232
+ # YAML fixtures that don't specify an ID get some extra features:
233
+ #
234
+ # * Stable, autogenerated IDs
235
+ # * Label references for associations (belongs_to, has_one, has_many)
236
+ # * HABTM associations as inline lists
237
+ # * Autofilled timestamp columns
238
+ # * Fixture label interpolation
239
+ # * Support for YAML defaults
240
+ #
241
+ # == Stable, autogenerated IDs
242
+ #
243
+ # Here, have a monkey fixture:
244
+ #
245
+ # george:
246
+ # id: 1
247
+ # name: George the Monkey
248
+ #
249
+ # reginald:
250
+ # id: 2
251
+ # name: Reginald the Pirate
252
+ #
253
+ # Each of these fixtures has two unique identifiers: one for the database
254
+ # and one for the humans. Why don't we generate the primary key instead?
255
+ # Hashing each fixture's label yields a consistent ID:
256
+ #
257
+ # george: # generated id: 503576764
258
+ # name: George the Monkey
259
+ #
260
+ # reginald: # generated id: 324201669
261
+ # name: Reginald the Pirate
262
+ #
263
+ # Active Record looks at the fixture's model class, discovers the correct
264
+ # primary key, and generates it right before inserting the fixture
265
+ # into the database.
266
+ #
267
+ # The generated ID for a given label is constant, so we can discover
268
+ # any fixture's ID without loading anything, as long as we know the label.
269
+ #
270
+ # == Label references for associations (belongs_to, has_one, has_many)
271
+ #
272
+ # Specifying foreign keys in fixtures can be very fragile, not to
273
+ # mention difficult to read. Since Active Record can figure out the ID of
274
+ # any fixture from its label, you can specify FK's by label instead of ID.
275
+ #
276
+ # === belongs_to
277
+ #
278
+ # Let's break out some more monkeys and pirates.
279
+ #
280
+ # ### in pirates.yml
281
+ #
282
+ # reginald:
283
+ # id: 1
284
+ # name: Reginald the Pirate
285
+ # monkey_id: 1
286
+ #
287
+ # ### in monkeys.yml
288
+ #
289
+ # george:
290
+ # id: 1
291
+ # name: George the Monkey
292
+ # pirate_id: 1
293
+ #
294
+ # Add a few more monkeys and pirates and break this into multiple files,
295
+ # and it gets pretty hard to keep track of what's going on. Let's
296
+ # use labels instead of IDs:
297
+ #
298
+ # ### in pirates.yml
299
+ #
300
+ # reginald:
301
+ # name: Reginald the Pirate
302
+ # monkey: george
303
+ #
304
+ # ### in monkeys.yml
305
+ #
306
+ # george:
307
+ # name: George the Monkey
308
+ # pirate: reginald
309
+ #
310
+ # Pow! All is made clear. Active Record reflects on the fixture's model class,
311
+ # finds all the +belongs_to+ associations, and allows you to specify
312
+ # a target *label* for the *association* (monkey: george) rather than
313
+ # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
314
+ #
315
+ # ==== Polymorphic belongs_to
316
+ #
317
+ # Supporting polymorphic relationships is a little bit more complicated, since
318
+ # Active Record needs to know what type your association is pointing at. Something
319
+ # like this should look familiar:
320
+ #
321
+ # ### in fruit.rb
322
+ #
323
+ # belongs_to :eater, :polymorphic => true
324
+ #
325
+ # ### in fruits.yml
326
+ #
327
+ # apple:
328
+ # id: 1
329
+ # name: apple
330
+ # eater_id: 1
331
+ # eater_type: Monkey
332
+ #
333
+ # Can we do better? You bet!
334
+ #
335
+ # apple:
336
+ # eater: george (Monkey)
337
+ #
338
+ # Just provide the polymorphic target type and Active Record will take care of the rest.
339
+ #
340
+ # === has_and_belongs_to_many
341
+ #
342
+ # Time to give our monkey some fruit.
343
+ #
344
+ # ### in monkeys.yml
345
+ #
346
+ # george:
347
+ # id: 1
348
+ # name: George the Monkey
349
+ #
350
+ # ### in fruits.yml
351
+ #
352
+ # apple:
353
+ # id: 1
354
+ # name: apple
355
+ #
356
+ # orange:
357
+ # id: 2
358
+ # name: orange
359
+ #
360
+ # grape:
361
+ # id: 3
362
+ # name: grape
363
+ #
364
+ # ### in fruits_monkeys.yml
365
+ #
366
+ # apple_george:
367
+ # fruit_id: 1
368
+ # monkey_id: 1
369
+ #
370
+ # orange_george:
371
+ # fruit_id: 2
372
+ # monkey_id: 1
373
+ #
374
+ # grape_george:
375
+ # fruit_id: 3
376
+ # monkey_id: 1
377
+ #
378
+ # Let's make the HABTM fixture go away.
379
+ #
380
+ # ### in monkeys.yml
381
+ #
382
+ # george:
383
+ # id: 1
384
+ # name: George the Monkey
385
+ # fruits: apple, orange, grape
386
+ #
387
+ # ### in fruits.yml
388
+ #
389
+ # apple:
390
+ # name: apple
391
+ #
392
+ # orange:
393
+ # name: orange
394
+ #
395
+ # grape:
396
+ # name: grape
397
+ #
398
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
399
+ # on George's fixture, but we could've just as easily specified a list
400
+ # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
401
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
402
+ # associations.
403
+ #
404
+ # == Autofilled timestamp columns
405
+ #
406
+ # If your table/model specifies any of Active Record's
407
+ # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
408
+ # they will automatically be set to <tt>Time.now</tt>.
409
+ #
410
+ # If you've set specific values, they'll be left alone.
411
+ #
412
+ # == Fixture label interpolation
413
+ #
414
+ # The label of the current fixture is always available as a column value:
415
+ #
416
+ # geeksomnia:
417
+ # name: Geeksomnia's Account
418
+ # subdomain: $LABEL
419
+ #
420
+ # Also, sometimes (like when porting older join table fixtures) you'll need
421
+ # to be able to get a hold of the identifier for a given label. ERB
422
+ # to the rescue:
423
+ #
424
+ # george_reginald:
425
+ # monkey_id: <%= Fixtures.identify(:reginald) %>
426
+ # pirate_id: <%= Fixtures.identify(:george) %>
427
+ #
428
+ # == Support for YAML defaults
429
+ #
430
+ # You probably already know how to use YAML to set and reuse defaults in
431
+ # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
432
+ #
433
+ # DEFAULTS: &DEFAULTS
434
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
435
+ #
436
+ # first:
437
+ # name: Smurf
438
+ # <<: *DEFAULTS
439
+ #
440
+ # second:
441
+ # name: Fraggle
442
+ # <<: *DEFAULTS
443
+ #
444
+ # Any fixture labeled "DEFAULTS" is safely ignored.
445
+
446
+ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
447
+ MAX_ID = 2 ** 30 - 1
448
+ DEFAULT_FILTER_RE = /\.ya?ml$/
449
+
450
+ @@all_cached_fixtures = {}
451
+
452
+ def self.reset_cache(connection = nil)
453
+ connection ||= Mongoid.database
454
+ @@all_cached_fixtures[connection.object_id] = {}
455
+ end
456
+
457
+ def self.cache_for_connection(connection)
458
+ @@all_cached_fixtures[connection.object_id] ||= {}
459
+ @@all_cached_fixtures[connection.object_id]
460
+ end
461
+
462
+ def self.fixture_is_cached?(connection, table_name)
463
+ cache_for_connection(connection)[table_name]
464
+ end
465
+
466
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
467
+ if keys_to_fetch
468
+ fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
469
+ else
470
+ fixtures = cache_for_connection(connection).values
471
+ end
472
+ fixtures.size > 1 ? fixtures : fixtures.first
473
+ end
474
+
475
+ def self.cache_fixtures(connection, fixtures_map)
476
+ cache_for_connection(connection).update(fixtures_map)
477
+ end
478
+
479
+ def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
480
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
481
+ if load_instances
482
+ Mongoid.silence do
483
+ fixtures.each do |name, fixture|
484
+ begin
485
+ object.instance_variable_set "@#{name}", fixture.find
486
+ rescue FixtureClassNotFound
487
+ nil
488
+ end
489
+ end
490
+ end
491
+ end
492
+ end
493
+
494
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
495
+ all_loaded_fixtures.each do |table_name, fixtures|
496
+ Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
497
+ end
498
+ end
499
+
500
+ cattr_accessor :all_loaded_fixtures
501
+ self.all_loaded_fixtures = {}
502
+
503
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
504
+ table_names = [table_names].flatten.map { |n| n.to_s }
505
+ connection = block_given? ? yield : Mongoid.database
506
+
507
+ table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
508
+ unless table_names_to_fetch.empty?
509
+ Mongoid.silence do
510
+ fixtures_map = {}
511
+ fixtures = table_names_to_fetch.map do |table_name|
512
+ fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
513
+ end
514
+
515
+ all_loaded_fixtures.update(fixtures_map)
516
+
517
+ # connection.transaction(:requires_new => true) do
518
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } if Mongoid.configure.delete_existing_fixtures_before_create
519
+ fixtures.each { |fixture| fixture.insert_fixtures }
520
+ # end
521
+
522
+ cache_fixtures(connection, fixtures_map)
523
+ end
524
+ end
525
+ cached_fixtures(connection, table_names)
526
+ end
527
+
528
+ # Returns a consistent, platform-independent identifier for +label+.
529
+ # Identifiers are positive integers less than 2^32.
530
+ def self.identify(label)
531
+ Zlib.crc32(label.to_s) % MAX_ID
532
+ end
533
+
534
+ attr_reader :table_name, :name
535
+
536
+ def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
537
+ @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
538
+ @name = table_name # preserve fixture base name
539
+ @class_name = class_name || @table_name.singularize.camelize
540
+ @table_name = @table_name
541
+ @table_name = class_name.table_name if class_name.respond_to?(:table_name)
542
+ @connection = class_name.connection if class_name.respond_to?(:connection)
543
+ read_fixture_files
544
+ end
545
+
546
+ def delete_existing_fixtures
547
+ Mongoid.master.collections.each{|col| col.drop if col.name == table_name }
548
+ end
549
+
550
+ def insert_fixtures
551
+ begin
552
+ now = Rails.configuration.time_zone.downcase.to_sym == :utc ? Time.now.utc : Time.now
553
+ rescue
554
+ # default to UTC if rails no existy
555
+ now = Time.now.utc
556
+ end
557
+
558
+ now = now.to_s(:db)
559
+
560
+ # allow a standard key to be used for doing defaults in YAML
561
+ if is_a?(Hash)
562
+ delete('DEFAULTS')
563
+ else
564
+ delete(assoc('DEFAULTS'))
565
+ end
566
+
567
+ # track any join tables we need to insert later
568
+ habtm_fixtures = Hash.new do |h, habtm|
569
+ h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
570
+ end
571
+
572
+ each do |label, fixture|
573
+ row = fixture.to_hash
574
+
575
+ if model_class && model_class < Mongoid
576
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
577
+ if model_class.record_timestamps
578
+ timestamp_column_names.each do |name|
579
+ row[name] = now unless row.key?(name)
580
+ end
581
+ end
582
+
583
+ # interpolate the fixture label
584
+ row.each do |key, value|
585
+ row[key] = label if value == "$LABEL"
586
+ end
587
+
588
+ # generate a primary key if necessary
589
+ if has_primary_key_column? && !row.include?(primary_key_name)
590
+ row[primary_key_name] = Fixtures.identify(label)
591
+ end
592
+
593
+ # If STI is used, find the correct subclass for association reflection
594
+ # reflection_class =
595
+ # if row.include?(inheritance_column_name)
596
+ # row[inheritance_column_name].constantize rescue model_class
597
+ # else
598
+ # model_class
599
+ # end
600
+ reflection_class = model_class
601
+
602
+ reflection_class.reflect_on_all_associations.each do |association|
603
+ case association.macro
604
+ when :belongs_to
605
+ # Do not replace association name with association foreign key if they are named the same
606
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
607
+
608
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
609
+ if association.options[:polymorphic]
610
+ if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
611
+ target_type = $1
612
+ target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
613
+
614
+ # support polymorphic belongs_to as "label (Type)"
615
+ row[target_type_name] = target_type
616
+ end
617
+ end
618
+
619
+ row[fk_name] = Fixtures.identify(value)
620
+ end
621
+ when :has_and_belongs_to_many
622
+ if (targets = row.delete(association.name.to_s))
623
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
624
+ join_fixtures = habtm_fixtures[association]
625
+
626
+ targets.each do |target|
627
+ join_fixtures["#{label}_#{target}"] = Fixture.new(
628
+ { association.primary_key_name => row[primary_key_name],
629
+ association.association_foreign_key => Fixtures.identify(target) },
630
+ nil, @connection)
631
+ end
632
+ end
633
+ end
634
+ end
635
+ end
636
+
637
+ Mongoid.insert_fixture(fixture, @table_name)
638
+ end
639
+
640
+ # insert any HABTM join tables we discovered
641
+ habtm_fixtures.values.each do |fixture|
642
+ fixture.delete_existing_fixtures
643
+ fixture.insert_fixtures
644
+ end
645
+ end
646
+
647
+ private
648
+ class HabtmFixtures < ::Fixtures #:nodoc:
649
+ def read_fixture_files; end
650
+ end
651
+
652
+ def model_class
653
+ unless defined?(@model_class)
654
+ @model_class =
655
+ if @class_name.nil? || @class_name.is_a?(Class)
656
+ @class_name
657
+ else
658
+ @class_name.constantize rescue nil
659
+ end
660
+ end
661
+
662
+ @model_class
663
+ end
664
+
665
+ def primary_key_name
666
+ @primary_key_name ||= (model_class && model_class.primary_key ? model_class.primary_key : 'id')
667
+ end
668
+
669
+ def has_primary_key_column?
670
+ @has_primary_key_column ||= model_class && primary_key_name && true
671
+ end
672
+
673
+ def timestamp_column_names
674
+ model_class.respond_to? :record_timestamps
675
+ end
676
+
677
+ def inheritance_column_name
678
+ @inheritance_column_name ||= nil
679
+ # @inheritance_column_name ||= model_class && model_class.inheritance_column
680
+ end
681
+
682
+ def column_names
683
+ # @column_names ||= @connection.columns(@table_name).collect(&:name)
684
+ @column_names ||= []
685
+ end
686
+
687
+ def read_fixture_files
688
+ if File.file?(yaml_file_path)
689
+ read_yaml_fixture_files
690
+ elsif File.file?(csv_file_path)
691
+ read_csv_fixture_files
692
+ end
693
+ end
694
+
695
+ def read_yaml_fixture_files
696
+ yaml_string = ""
697
+ Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
698
+ yaml_string << IO.read(subfixture_path)
699
+ end
700
+ yaml_string << IO.read(yaml_file_path)
701
+
702
+ if yaml = parse_yaml_string(yaml_string)
703
+ # If the file is an ordered map, extract its children.
704
+ yaml_value =
705
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
706
+ yaml.value
707
+ else
708
+ [yaml]
709
+ end
710
+
711
+ yaml_value.each do |fixture|
712
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
713
+ fixture.each do |name, data|
714
+ unless data
715
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
716
+ end
717
+
718
+ self[name] = Fixture.new(data, model_class, @connection)
719
+ end
720
+ end
721
+ end
722
+ end
723
+
724
+ def read_csv_fixture_files
725
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
726
+ header = reader.shift
727
+ i = 0
728
+ reader.each do |row|
729
+ data = {}
730
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
731
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
732
+ end
733
+ end
734
+
735
+ def yaml_file_path
736
+ "#{@fixture_path}.yml"
737
+ end
738
+
739
+ def csv_file_path
740
+ @fixture_path + ".csv"
741
+ end
742
+
743
+ def yaml_fixtures_key(path)
744
+ File.basename(@fixture_path).split(".").first
745
+ end
746
+
747
+ def parse_yaml_string(fixture_content)
748
+ YAML::load(erb_render(fixture_content))
749
+ rescue => error
750
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
751
+ end
752
+
753
+ def erb_render(fixture_content)
754
+ ERB.new(fixture_content).result
755
+ end
756
+ end
757
+
758
+ class Fixture #:nodoc:
759
+ include Enumerable
760
+
761
+ class FixtureError < StandardError #:nodoc:
762
+ end
763
+
764
+ class FormatError < FixtureError #:nodoc:
765
+ end
766
+
767
+ attr_reader :model_class
768
+
769
+ def initialize(fixture, model_class, connection = Mongoid.database)
770
+ @connection = connection
771
+ @fixture = fixture
772
+ @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
773
+ end
774
+
775
+ def class_name
776
+ @model_class.name if @model_class
777
+ end
778
+
779
+ def each
780
+ @fixture.each { |item| yield item }
781
+ end
782
+
783
+ def [](key)
784
+ @fixture[key]
785
+ end
786
+
787
+ def to_hash
788
+ @fixture
789
+ end
790
+
791
+ def key_list
792
+ columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
793
+ columns.join(", ")
794
+ end
795
+
796
+ def value_list
797
+ list = @fixture.inject([]) do |fixtures, (key, value)|
798
+ col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(Mongoid)
799
+ fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
800
+ end
801
+ list * ', '
802
+ end
803
+
804
+ def find
805
+ forced_primary_key = model_class.primary_key ? model_class.primary_key : 'id'
806
+ if model_class
807
+ model_class.find(forced_primary_key)
808
+ else
809
+ raise FixtureClassNotFound, "No class attached to find."
810
+ end
811
+ end
812
+ end
813
+
814
+ module Mongoid
815
+ module TestFixtures
816
+ extend ActiveSupport::Concern
817
+
818
+ included do
819
+ setup :setup_fixtures
820
+ teardown :teardown_fixtures
821
+
822
+ superclass_delegating_accessor :fixture_path
823
+ superclass_delegating_accessor :fixture_table_names
824
+ superclass_delegating_accessor :fixture_class_names
825
+ superclass_delegating_accessor :use_transactional_fixtures
826
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
827
+ superclass_delegating_accessor :pre_loaded_fixtures
828
+
829
+ self.fixture_table_names = []
830
+ self.use_transactional_fixtures = false
831
+ self.use_instantiated_fixtures = true
832
+ self.pre_loaded_fixtures = false
833
+
834
+ self.fixture_class_names = {}
835
+ end
836
+
837
+ module ClassMethods
838
+ def set_fixture_class(class_names = {})
839
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
840
+ end
841
+
842
+ def fixtures(*table_names)
843
+ if table_names.first == :all
844
+ table_names = Dir["#{Rails.root}#{fixture_path}/*.yml"] + Dir["#{Rails.root}#{fixture_path}/*.csv"]
845
+ table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
846
+ else
847
+ table_names = table_names.flatten.map { |n| n.to_s }
848
+ end
849
+
850
+ self.fixture_table_names |= table_names
851
+ require_fixture_classes(table_names)
852
+ setup_fixture_accessors(table_names)
853
+ end
854
+
855
+ def try_to_load_dependency(file_name)
856
+ require_dependency file_name
857
+ rescue LoadError => e
858
+ # Let's hope the developer has included it himself
859
+
860
+ # Let's warn in case this is a subdependency, otherwise
861
+ # subdependency error messages are totally cryptic
862
+ mongoid_logger = Mongoid.config.send :logger
863
+ if mongoid_logger
864
+ mongoid_logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
865
+ end
866
+ end
867
+
868
+ def require_fixture_classes(table_names = nil)
869
+ (table_names || fixture_table_names).each do |table_name|
870
+ file_name = table_name.to_s
871
+ try_to_load_dependency(file_name.singularize) # assume singularized model name and pluralized fixture file name
872
+ end
873
+ end
874
+
875
+ def setup_fixture_accessors(table_names = nil)
876
+ table_names = [table_names] if table_names && !table_names.respond_to?(:each)
877
+ (table_names || fixture_table_names).each do |table_name|
878
+ table_name = table_name.to_s.tr('.', '_')
879
+
880
+ define_method(table_name) do |*fixtures|
881
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
882
+
883
+ @fixture_cache[table_name] ||= {}
884
+
885
+ instances = fixtures.map do |fixture|
886
+ @fixture_cache[table_name].delete(fixture) if force_reload
887
+
888
+ if @loaded_fixtures[table_name][fixture.to_s]
889
+ @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
890
+ else
891
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
892
+ end
893
+ end
894
+
895
+ instances.size == 1 ? instances.first : instances
896
+ end
897
+ end
898
+ end
899
+
900
+ def uses_transaction(*methods)
901
+ @uses_transaction = [] unless defined?(@uses_transaction)
902
+ @uses_transaction.concat methods.map(&:to_s)
903
+ end
904
+
905
+ def uses_transaction?(method)
906
+ @uses_transaction = [] unless defined?(@uses_transaction)
907
+ @uses_transaction.include?(method.to_s)
908
+ end
909
+ end
910
+
911
+ def run_in_transaction?
912
+ # use_transactional_fixtures &&
913
+ # !self.class.uses_transaction?(method_name)
914
+ false
915
+ end
916
+
917
+ def setup_fixtures
918
+ return unless defined?(Mongoid) && !Mongoid.config.blank?
919
+
920
+ if pre_loaded_fixtures && !use_transactional_fixtures
921
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
922
+ end
923
+
924
+ @fixture_cache = {}
925
+ @@already_loaded_fixtures ||= {}
926
+
927
+ # Load fixtures files once for all tests.
928
+ if use_instantiated_fixtures && Fixtures.class_variable_get(:@@all_cached_fixtures).size == 0
929
+ Fixtures.reset_cache
930
+ @@already_loaded_fixtures[self.class] = nil
931
+ load_fixtures && instantiate_fixtures
932
+ end
933
+
934
+ end
935
+
936
+ def teardown_fixtures
937
+ return unless defined?(Mongoid) && !Mongoid.config.blank?
938
+
939
+ unless run_in_transaction?
940
+ Fixtures.reset_cache
941
+ end
942
+
943
+ # ActiveRecord::Base.clear_active_connections!
944
+ end
945
+
946
+ private
947
+ def load_fixtures
948
+ @loaded_fixtures = {}
949
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
950
+ unless fixtures.nil?
951
+ if fixtures.instance_of?(Fixtures)
952
+ @loaded_fixtures[fixtures.name] = fixtures
953
+ else
954
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
955
+ end
956
+ end
957
+ end
958
+
959
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
960
+ @@required_fixture_classes = false
961
+
962
+ def instantiate_fixtures
963
+ if pre_loaded_fixtures
964
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
965
+ unless @@required_fixture_classes
966
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
967
+ @@required_fixture_classes = true
968
+ end
969
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
970
+ else
971
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
972
+ @loaded_fixtures.each do |table_name, fixtures|
973
+ Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
974
+ end
975
+ end
976
+ end
977
+
978
+ def load_instances?
979
+ use_instantiated_fixtures != :no_instances
980
+ end
981
+ end
982
+ end