bigrecord 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +44 -0
  3. data/Rakefile +17 -0
  4. data/VERSION +1 -0
  5. data/doc/bigrecord_specs.rdoc +36 -0
  6. data/doc/getting_started.rdoc +157 -0
  7. data/examples/bigrecord.yml +25 -0
  8. data/generators/bigrecord/bigrecord_generator.rb +17 -0
  9. data/generators/bigrecord/templates/bigrecord.rake +47 -0
  10. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
  11. data/generators/bigrecord_migration/templates/migration.rb +9 -0
  12. data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
  13. data/generators/bigrecord_model/templates/migration.rb +13 -0
  14. data/generators/bigrecord_model/templates/model.rb +7 -0
  15. data/generators/bigrecord_model/templates/model_spec.rb +12 -0
  16. data/init.rb +9 -0
  17. data/install.rb +22 -0
  18. data/lib/big_record/abstract_base.rb +1088 -0
  19. data/lib/big_record/action_view_extensions.rb +266 -0
  20. data/lib/big_record/ar_associations/association_collection.rb +194 -0
  21. data/lib/big_record/ar_associations/association_proxy.rb +158 -0
  22. data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
  23. data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
  24. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
  25. data/lib/big_record/ar_associations/has_many_association.rb +191 -0
  26. data/lib/big_record/ar_associations/has_one_association.rb +80 -0
  27. data/lib/big_record/ar_associations.rb +1608 -0
  28. data/lib/big_record/ar_reflection.rb +223 -0
  29. data/lib/big_record/attribute_methods.rb +75 -0
  30. data/lib/big_record/base.rb +618 -0
  31. data/lib/big_record/br_associations/association_collection.rb +194 -0
  32. data/lib/big_record/br_associations/association_proxy.rb +153 -0
  33. data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
  34. data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
  35. data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
  36. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
  37. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
  38. data/lib/big_record/br_associations/has_one_association.rb +80 -0
  39. data/lib/big_record/br_associations.rb +978 -0
  40. data/lib/big_record/br_reflection.rb +151 -0
  41. data/lib/big_record/callbacks.rb +367 -0
  42. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
  43. data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
  44. data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
  45. data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
  46. data/lib/big_record/connection_adapters/column.rb +491 -0
  47. data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
  48. data/lib/big_record/connection_adapters/view.rb +27 -0
  49. data/lib/big_record/connection_adapters.rb +10 -0
  50. data/lib/big_record/deletion.rb +73 -0
  51. data/lib/big_record/dynamic_schema.rb +92 -0
  52. data/lib/big_record/embedded.rb +71 -0
  53. data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
  54. data/lib/big_record/family_span_columns.rb +89 -0
  55. data/lib/big_record/fixtures.rb +1025 -0
  56. data/lib/big_record/migration.rb +380 -0
  57. data/lib/big_record/routing_ext.rb +65 -0
  58. data/lib/big_record/timestamp.rb +51 -0
  59. data/lib/big_record/validations.rb +830 -0
  60. data/lib/big_record.rb +125 -0
  61. data/lib/bigrecord.rb +1 -0
  62. data/rails/init.rb +9 -0
  63. data/spec/connections/bigrecord.yml +13 -0
  64. data/spec/connections/cassandra/connection.rb +2 -0
  65. data/spec/connections/hbase/connection.rb +2 -0
  66. data/spec/debug.log +281 -0
  67. data/spec/integration/br_associations_spec.rb +80 -0
  68. data/spec/lib/animal.rb +12 -0
  69. data/spec/lib/book.rb +10 -0
  70. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
  71. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
  72. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
  73. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
  74. data/spec/lib/company.rb +14 -0
  75. data/spec/lib/embedded/web_link.rb +12 -0
  76. data/spec/lib/employee.rb +33 -0
  77. data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
  78. data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
  79. data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
  80. data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
  81. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
  82. data/spec/lib/novel.rb +5 -0
  83. data/spec/lib/zoo.rb +17 -0
  84. data/spec/spec.opts +4 -0
  85. data/spec/spec_helper.rb +55 -0
  86. data/spec/unit/abstract_base_spec.rb +287 -0
  87. data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
  88. data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
  89. data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
  90. data/spec/unit/ar_associations_spec.rb +8 -0
  91. data/spec/unit/base_spec.rb +6 -0
  92. data/spec/unit/br_associations_spec.rb +58 -0
  93. data/spec/unit/embedded_spec.rb +43 -0
  94. data/spec/unit/find_spec.rb +34 -0
  95. data/spec/unit/hash_helper_spec.rb +44 -0
  96. data/spec/unit/migration_spec.rb +144 -0
  97. data/spec/unit/model_spec.rb +315 -0
  98. data/spec/unit/validations_spec.rb +182 -0
  99. data/tasks/bigrecord_tasks.rake +47 -0
  100. data/tasks/data_store.rb +46 -0
  101. data/tasks/gem.rb +22 -0
  102. data/tasks/rdoc.rb +8 -0
  103. data/tasks/spec.rb +34 -0
  104. metadata +189 -0
@@ -0,0 +1,1025 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+ require 'csv'
4
+
5
+ module YAML #:nodoc:
6
+ class Omap #:nodoc:
7
+ def keys; map { |k, v| k } end
8
+ def values; map { |k, v| v } end
9
+ end
10
+ end
11
+
12
+ if defined? BigRecord
13
+ class BigRecordFixtureClassNotFound < BigRecord::BigRecordError #:nodoc:
14
+ end
15
+ else
16
+ class BigRecordFixtureClassNotFound < StandardError #:nodoc:
17
+ end
18
+ end
19
+
20
+ # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
21
+ #
22
+ # 1. YAML fixtures
23
+ # 2. CSV fixtures
24
+ # 3. Single-file fixtures
25
+ #
26
+ # = YAML fixtures
27
+ #
28
+ # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
29
+ # in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
30
+ #
31
+ # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
32
+ # by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
33
+ # put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
34
+ # "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
35
+ #
36
+ # rubyonrails:
37
+ # id: 1
38
+ # name: Ruby on Rails
39
+ # url: http://www.rubyonrails.org
40
+ #
41
+ # google:
42
+ # id: 2
43
+ # name: Google
44
+ # url: http://www.google.com
45
+ #
46
+ # This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
47
+ # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
48
+ # pleasure.
49
+ #
50
+ # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
51
+ # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
52
+ # This is commonly needed for tree structures. Example:
53
+ #
54
+ # --- !omap
55
+ # - parent:
56
+ # id: 1
57
+ # parent_id: NULL
58
+ # title: Parent
59
+ # - child:
60
+ # id: 2
61
+ # parent_id: 1
62
+ # title: Child
63
+ #
64
+ # = CSV fixtures
65
+ #
66
+ # Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
67
+ # in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
68
+ #
69
+ # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
70
+ # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
71
+ # of the actual data (1 per line). Here's an example:
72
+ #
73
+ # id, name, url
74
+ # 1, Ruby On Rails, http://www.rubyonrails.org
75
+ # 2, Google, http://www.google.com
76
+ #
77
+ # Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
78
+ # need to use a double quote character, you must escape it with another double quote.
79
+ #
80
+ # Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
81
+ # fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
82
+ # number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
83
+ # "web_site_2".
84
+ #
85
+ # Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
86
+ # have existing data somewhere already.
87
+ #
88
+ # = Single-file fixtures
89
+ #
90
+ # This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
91
+ # Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
92
+ # appointed by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
93
+ # put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
94
+ # model).
95
+ #
96
+ # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
97
+ # extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the
98
+ # above example might look like:
99
+ #
100
+ # web_sites/google
101
+ # web_sites/yahoo.txt
102
+ # web_sites/ruby-on-rails
103
+ #
104
+ # The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
105
+ # of "name => value". Here's an example of the ruby-on-rails fixture above:
106
+ #
107
+ # id => 1
108
+ # name => Ruby on Rails
109
+ # url => http://www.rubyonrails.org
110
+ #
111
+ # = Using Fixtures
112
+ #
113
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
114
+ # fixtures, but first let's take a look at a sample unit test:
115
+ #
116
+ # require 'web_site'
117
+ #
118
+ # class WebSiteTest < Test::Unit::TestCase
119
+ # def test_web_site_count
120
+ # assert_equal 2, WebSite.count
121
+ # end
122
+ # end
123
+ #
124
+ # As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
125
+ # easiest way to add fixtures to the database:
126
+ #
127
+ # ...
128
+ # class WebSiteTest < Test::Unit::TestCase
129
+ # fixtures :web_sites # add more by separating the symbols with commas
130
+ # ...
131
+ #
132
+ # By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
133
+ # the testing environment to automatically load the appropriate fixtures into the database before each test.
134
+ # To ensure consistent data, the environment deletes the fixtures before running the load.
135
+ #
136
+ # In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
137
+ # of the test case. It is named after the symbol... so, in our example, there would be a hash available called
138
+ # @web_sites. This is where the "fixture name" comes into play.
139
+ #
140
+ # On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
141
+ # So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
142
+ #
143
+ # # test if the object created from the fixture data has the same attributes as the data itself
144
+ # def test_find
145
+ # assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
146
+ # end
147
+ #
148
+ # As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
149
+ # "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
150
+ # from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
151
+ # fixtures available as instance variables @web_site_1 and @web_site_2.
152
+ #
153
+ # If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
154
+ #
155
+ # - to completely disable instantiated fixtures:
156
+ # self.use_instantiated_fixtures = false
157
+ #
158
+ # - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
159
+ # self.use_instantiated_fixtures = :no_instances
160
+ #
161
+ # Even if auto-instantiated fixtures are disabled, you can still access them
162
+ # by name via special dynamic methods. Each method has the same name as the
163
+ # model, and accepts the name of the fixture to instantiate:
164
+ #
165
+ # fixtures :web_sites
166
+ #
167
+ # def test_find
168
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
169
+ # end
170
+ #
171
+ # = Dynamic fixtures with ERb
172
+ #
173
+ # 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
174
+ # mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
175
+ #
176
+ # <% for i in 1..1000 %>
177
+ # fix_<%= i %>:
178
+ # id: <%= i %>
179
+ # name: guy_<%= 1 %>
180
+ # <% end %>
181
+ #
182
+ # This will create 1000 very simple YAML fixtures.
183
+ #
184
+ # Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
185
+ # This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
186
+ # sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
187
+ # is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
188
+ #
189
+ # = Transactional fixtures
190
+ #
191
+ # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
192
+ # They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
193
+ #
194
+ # class FooTest < Test::Unit::TestCase
195
+ # self.use_transactional_fixtures = true
196
+ # self.use_instantiated_fixtures = false
197
+ #
198
+ # fixtures :foos
199
+ #
200
+ # def test_godzilla
201
+ # assert !Foo.find(:all).empty?
202
+ # Foo.destroy_all
203
+ # assert Foo.find(:all).empty?
204
+ # end
205
+ #
206
+ # def test_godzilla_aftermath
207
+ # assert !Foo.find(:all).empty?
208
+ # end
209
+ # end
210
+ #
211
+ # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
212
+ # 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.
213
+ #
214
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
215
+ # access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
216
+ #
217
+ # When *not* to use transactional fixtures:
218
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
219
+ # particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
220
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
221
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
222
+ # Use InnoDB, MaxDB, or NDB instead.
223
+ #
224
+ # = Advanced YAML Fixtures
225
+ #
226
+ # YAML fixtures that don't specify an ID get some extra features:
227
+ #
228
+ # * Stable, autogenerated ID's
229
+ # * Label references for associations (belongs_to, has_one, has_many)
230
+ # * HABTM associations as inline lists
231
+ # * Autofilled timestamp columns
232
+ # * Fixture label interpolation
233
+ # * Support for YAML defaults
234
+ #
235
+ # == Stable, autogenerated ID's
236
+ #
237
+ # Here, have a monkey fixture:
238
+ #
239
+ # george:
240
+ # id: 1
241
+ # name: George the Monkey
242
+ #
243
+ # reginald:
244
+ # id: 2
245
+ # name: Reginald the Pirate
246
+ #
247
+ # Each of these fixtures has two unique identifiers: one for the database
248
+ # and one for the humans. Why don't we generate the primary key instead?
249
+ # Hashing each fixture's label yields a consistent ID:
250
+ #
251
+ # george: # generated id: 503576764
252
+ # name: George the Monkey
253
+ #
254
+ # reginald: # generated id: 324201669
255
+ # name: Reginald the Pirate
256
+ #
257
+ # ActiveRecord looks at the fixture's model class, discovers the correct
258
+ # primary key, and generates it right before inserting the fixture
259
+ # into the database.
260
+ #
261
+ # The generated ID for a given label is constant, so we can discover
262
+ # any fixture's ID without loading anything, as long as we know the label.
263
+ #
264
+ # == Label references for associations (belongs_to, has_one, has_many)
265
+ #
266
+ # Specifying foreign keys in fixtures can be very fragile, not to
267
+ # mention difficult to read. Since ActiveRecord can figure out the ID of
268
+ # any fixture from its label, you can specify FK's by label instead of ID.
269
+ #
270
+ # === belongs_to
271
+ #
272
+ # Let's break out some more monkeys and pirates.
273
+ #
274
+ # ### in pirates.yml
275
+ #
276
+ # reginald:
277
+ # id: 1
278
+ # name: Reginald the Pirate
279
+ # monkey_id: 1
280
+ #
281
+ # ### in monkeys.yml
282
+ #
283
+ # george:
284
+ # id: 1
285
+ # name: George the Monkey
286
+ # pirate_id: 1
287
+ #
288
+ # Add a few more monkeys and pirates and break this into multiple files,
289
+ # and it gets pretty hard to keep track of what's going on. Let's
290
+ # use labels instead of ID's:
291
+ #
292
+ # ### in pirates.yml
293
+ #
294
+ # reginald:
295
+ # name: Reginald the Pirate
296
+ # monkey: george
297
+ #
298
+ # ### in monkeys.yml
299
+ #
300
+ # george:
301
+ # name: George the Monkey
302
+ # pirate: reginald
303
+ #
304
+ # Pow! All is made clear. ActiveRecord reflects on the fixture's model class,
305
+ # finds all the +belongs_to+ associations, and allows you to specify
306
+ # a target *label* for the *association* (monkey: george) rather than
307
+ # a target *id* for the *FK* (monkey_id: 1).
308
+ #
309
+ # ==== Polymorphic belongs_to
310
+ #
311
+ # Supporting polymorphic relationships is a little bit more complicated, since
312
+ # ActiveRecord needs to know what type your association is pointing at. Something
313
+ # like this should look familiar:
314
+ #
315
+ # ### in fruit.rb
316
+ #
317
+ # belongs_to :eater, :polymorphic => true
318
+ #
319
+ # ### in fruits.yml
320
+ #
321
+ # apple:
322
+ # id: 1
323
+ # name: apple
324
+ # eater_id: 1
325
+ # eater_type: Monkey
326
+ #
327
+ # Can we do better? You bet!
328
+ #
329
+ # apple:
330
+ # eater: george (Monkey)
331
+ #
332
+ # Just provide the polymorphic target type and ActiveRecord will take care of the rest.
333
+ #
334
+ # === has_and_belongs_to_many
335
+ #
336
+ # Time to give our monkey some fruit.
337
+ #
338
+ # ### in monkeys.yml
339
+ #
340
+ # george:
341
+ # id: 1
342
+ # name: George the Monkey
343
+ # pirate_id: 1
344
+ #
345
+ # ### in fruits.yml
346
+ #
347
+ # apple:
348
+ # id: 1
349
+ # name: apple
350
+ #
351
+ # orange:
352
+ # id: 2
353
+ # name: orange
354
+ #
355
+ # grape:
356
+ # id: 3
357
+ # name: grape
358
+ #
359
+ # ### in fruits_monkeys.yml
360
+ #
361
+ # apple_george:
362
+ # fruit_id: 1
363
+ # monkey_id: 1
364
+ #
365
+ # orange_george:
366
+ # fruit_id: 2
367
+ # monkey_id: 1
368
+ #
369
+ # grape_george:
370
+ # fruit_id: 3
371
+ # monkey_id: 1
372
+ #
373
+ # Let's make the HABTM fixture go away.
374
+ #
375
+ # ### in monkeys.yml
376
+ #
377
+ # george:
378
+ # name: George the Monkey
379
+ # pirate: reginald
380
+ # fruits: apple, orange, grape
381
+ #
382
+ # ### in fruits.yml
383
+ #
384
+ # apple:
385
+ # name: apple
386
+ #
387
+ # orange:
388
+ # name: orange
389
+ #
390
+ # grape:
391
+ # name: grape
392
+ #
393
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
394
+ # on George's fixture, but we could've just as easily specified a list
395
+ # of monkeys on each fruit. As with +belongs_to+, ActiveRecord reflects on
396
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
397
+ # associations.
398
+ #
399
+ # == Autofilled timestamp columns
400
+ #
401
+ # If your table/model specifies any of ActiveRecord's
402
+ # standard timestamp columns (created_at, created_on, updated_at, updated_on),
403
+ # they will automatically be set to Time.now.
404
+ #
405
+ # If you've set specific values, they'll be left alone.
406
+ #
407
+ # == Fixture label interpolation
408
+ #
409
+ # The label of the current fixture is always available as a column value:
410
+ #
411
+ # geeksomnia:
412
+ # name: Geeksomnia's Account
413
+ # subdomain: $LABEL
414
+ #
415
+ # Also, sometimes (like when porting older join table fixtures) you'll need
416
+ # to be able to get ahold of the identifier for a given label. ERB
417
+ # to the rescue:
418
+ #
419
+ # george_reginald:
420
+ # monkey_id: <%= Fixtures.identify(:reginald) %>
421
+ # pirate_id: <%= Fixtures.identify(:george) %>
422
+ #
423
+ # == Support for YAML defaults
424
+ #
425
+ # You probably already know how to use YAML to set and reuse defaults in
426
+ # your +database.yml+ file,. You can use the same technique in your fixtures:
427
+ #
428
+ # DEFAULTS: &DEFAULTS
429
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
430
+ #
431
+ # first:
432
+ # name: Smurf
433
+ # <<: *DEFAULTS
434
+ #
435
+ # second:
436
+ # name: Fraggle
437
+ # <<: *DEFAULTS
438
+ #
439
+ # Any fixture labeled "DEFAULTS" is safely ignored.
440
+
441
+ class BigRecordFixtures < YAML::Omap
442
+ DEFAULT_FILTER_RE = /\.ya?ml$/
443
+
444
+ @@all_cached_big_record_fixtures = {}
445
+
446
+ def self.reset_cache(connection = nil)
447
+ connection ||= BigRecord::Base.connection
448
+ @@all_cached_big_record_fixtures[connection.object_id] = {}
449
+ end
450
+
451
+ def self.cache_for_connection(connection)
452
+ @@all_cached_big_record_fixtures[connection.object_id] ||= {}
453
+ @@all_cached_big_record_fixtures[connection.object_id]
454
+ end
455
+
456
+ def self.fixture_is_cached?(connection, table_name)
457
+ cache_for_connection(connection)[table_name]
458
+ end
459
+
460
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
461
+ if keys_to_fetch
462
+ fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
463
+ else
464
+ fixtures = cache_for_connection(connection).values
465
+ end
466
+ fixtures.size > 1 ? fixtures : fixtures.first
467
+ end
468
+
469
+ def self.cache_fixtures(connection, fixtures)
470
+ cache_for_connection(connection).update(fixtures.index_by(&:table_name))
471
+ end
472
+
473
+ def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
474
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
475
+ if load_instances
476
+ BigRecord::Base.silence do
477
+ fixtures.each do |name, fixture|
478
+ begin
479
+ object.instance_variable_set "@#{name}", fixture.find
480
+ rescue BigRecordFixtureClassNotFound
481
+ nil
482
+ end
483
+ end
484
+ end
485
+ end
486
+ end
487
+
488
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
489
+ all_loaded_fixtures.each do |table_name, fixtures|
490
+ BigRecordFixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
491
+ end
492
+ end
493
+
494
+ cattr_accessor :all_loaded_fixtures
495
+ self.all_loaded_fixtures = {}
496
+
497
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
498
+ table_names = [table_names].flatten.map { |n| n.to_s }
499
+ connection = block_given? ? yield : BigRecord::Base.connection
500
+
501
+ table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
502
+
503
+ unless table_names_to_fetch.empty?
504
+ BigRecord::Base.silence do
505
+ connection.disable_referential_integrity do
506
+ fixtures_map = {}
507
+
508
+ fixtures = table_names_to_fetch.map do |table_name|
509
+ fixtures_map[table_name] = BigRecordFixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
510
+ end
511
+
512
+ all_loaded_fixtures.update(fixtures_map)
513
+
514
+ # connection.transaction(Thread.current['open_transactions'].to_i == 0) do
515
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
516
+ fixtures.each { |fixture| fixture.insert_fixtures }
517
+
518
+ # Cap primary key sequences to max(pk).
519
+ if connection.respond_to?(:reset_pk_sequence!)
520
+ table_names.each do |table_name|
521
+ connection.reset_pk_sequence!(table_name)
522
+ end
523
+ end
524
+ # end
525
+
526
+ cache_fixtures(connection, fixtures)
527
+ end
528
+ end
529
+ end
530
+ cached_fixtures(connection, table_names)
531
+ end
532
+
533
+ # Returns a consistent identifier for +label+. This will always
534
+ # be a positive integer, and will always be the same for a given
535
+ # label, assuming the same OS, platform, and version of Ruby.
536
+ def self.identify(label)
537
+ label.to_s.hash.abs
538
+ end
539
+
540
+ attr_reader :table_name
541
+
542
+ def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
543
+ @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
544
+ @class_name = class_name ||
545
+ (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
546
+ @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
547
+ @table_name = class_name.table_name if class_name.respond_to?(:table_name)
548
+ @connection = class_name.connection if class_name.respond_to?(:connection)
549
+ read_fixture_files
550
+ end
551
+
552
+ def delete_existing_fixtures
553
+ @connection.get_consecutive_rows(table_name, nil, nil, ["attribute:"]).each do |row|
554
+ @connection.delete(table_name, row['id'])
555
+ end
556
+ end
557
+
558
+ def insert_fixtures
559
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
560
+ now = now.to_s(:db)
561
+
562
+ # allow a standard key to be used for doing defaults in YAML
563
+ delete(assoc("DEFAULTS"))
564
+
565
+ # track any join tables we need to insert later
566
+ habtm_fixtures = Hash.new do |h, habtm|
567
+ h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
568
+ end
569
+
570
+ each do |label, fixture|
571
+ row = fixture.to_hash
572
+
573
+ if model_class && model_class < ActiveRecord::Base
574
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
575
+ if model_class.record_timestamps
576
+ timestamp_column_names.each do |name|
577
+ row[name] = now unless row.key?(name)
578
+ end
579
+ end
580
+
581
+ # interpolate the fixture label
582
+ row.each do |key, value|
583
+ row[key] = label if value == "$LABEL"
584
+ end
585
+
586
+ # generate a primary key if necessary
587
+ if has_primary_key_column? && !row.include?(primary_key_name)
588
+ row[primary_key_name] = BigRecordFixtures.identify(label)
589
+ end
590
+
591
+ # If STI is used, find the correct subclass for association reflection
592
+ reflection_class =
593
+ if row.include?(inheritance_column_name)
594
+ row[inheritance_column_name].constantize rescue model_class
595
+ else
596
+ model_class
597
+ end
598
+
599
+ reflection_class.reflect_on_all_associations.each do |association|
600
+ case association.macro
601
+ when :belongs_to
602
+ # Do not replace association name with association foreign key if they are named the same
603
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
604
+
605
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
606
+ if association.options[:polymorphic]
607
+ if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
608
+ target_type = $1
609
+ target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
610
+
611
+ # support polymorphic belongs_to as "label (Type)"
612
+ row[target_type_name] = target_type
613
+ end
614
+ end
615
+
616
+ row[fk_name] = BigRecordFixtures.identify(value)
617
+ end
618
+ when :has_and_belongs_to_many
619
+ if (targets = row.delete(association.name.to_s))
620
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
621
+ join_fixtures = habtm_fixtures[association]
622
+
623
+ targets.each do |target|
624
+ join_fixtures["#{label}_#{target}"] = BigRecordFixture.new(
625
+ { association.primary_key_name => row[primary_key_name],
626
+ association.association_foreign_key => BigRecordFixtures.identify(target) }, nil)
627
+ end
628
+ end
629
+ end
630
+ end
631
+ end
632
+
633
+ @connection.insert_fixture(fixture, @table_name)
634
+ end
635
+
636
+ # insert any HABTM join tables we discovered
637
+ habtm_fixtures.values.each do |fixture|
638
+ fixture.delete_existing_fixtures
639
+ fixture.insert_fixtures
640
+ end
641
+ end
642
+
643
+ private
644
+ class HabtmFixtures < ::BigRecordFixtures #:nodoc:
645
+ def read_fixture_files; end
646
+ end
647
+
648
+ def model_class
649
+ @model_class ||= @class_name.is_a?(Class) ?
650
+ @class_name : @class_name.constantize rescue nil
651
+ end
652
+
653
+ def primary_key_name
654
+ @primary_key_name ||= model_class && model_class.primary_key
655
+ end
656
+
657
+ def has_primary_key_column?
658
+ @has_primary_key_column ||= model_class && primary_key_name &&
659
+ model_class.columns.find { |c| c.name == primary_key_name }
660
+ end
661
+
662
+ def timestamp_column_names
663
+ @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
664
+ column_names.include?(name)
665
+ end
666
+ end
667
+
668
+ def inheritance_column_name
669
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
670
+ end
671
+
672
+ def column_names
673
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
674
+ end
675
+
676
+ def read_fixture_files
677
+ if File.file?(yaml_file_path)
678
+ read_yaml_fixture_files
679
+ elsif File.file?(csv_file_path)
680
+ read_csv_fixture_files
681
+ else
682
+ # Standard fixtures
683
+ Dir.entries(@fixture_path).each do |file|
684
+ path = File.join(@fixture_path, file)
685
+ if File.file?(path) and file !~ @file_filter
686
+ self[file] = BigRecordFixture.new(path, @class_name)
687
+ end
688
+ end
689
+ end
690
+ end
691
+
692
+ def read_yaml_fixture_files
693
+ yaml_string = ""
694
+ Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
695
+ yaml_string << IO.read(subfixture_path)
696
+ end
697
+ yaml_string << IO.read(yaml_file_path)
698
+
699
+ if yaml = parse_yaml_string(yaml_string)
700
+ # If the file is an ordered map, extract its children.
701
+ yaml_value =
702
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
703
+ yaml.value
704
+ else
705
+ [yaml]
706
+ end
707
+
708
+ yaml_value.each do |fixture|
709
+ fixture.each do |name, data|
710
+ unless data
711
+ raise BigRecordFixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
712
+ end
713
+
714
+ self[name] = BigRecordFixture.new(data, @class_name)
715
+ end
716
+ end
717
+ end
718
+ end
719
+
720
+ def read_csv_fixture_files
721
+ reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
722
+ header = reader.shift
723
+ i = 0
724
+ reader.each do |row|
725
+ data = {}
726
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
727
+ self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= BigRecordFixture.new(data, @class_name)
728
+ end
729
+ end
730
+
731
+ def yaml_file_path
732
+ "#{@fixture_path}.yml"
733
+ end
734
+
735
+ def csv_file_path
736
+ @fixture_path + ".csv"
737
+ end
738
+
739
+ def yaml_fixtures_key(path)
740
+ File.basename(@fixture_path).split(".").first
741
+ end
742
+
743
+ def parse_yaml_string(fixture_content)
744
+ YAML::load(erb_render(fixture_content))
745
+ rescue => error
746
+ raise BigRecordFixture::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}"
747
+ end
748
+
749
+ #FIXME: Turn ERB render back on, conditionnaly...
750
+ def erb_render(fixture_content)
751
+ fixture_content
752
+ #ERB.new(fixture_content).result
753
+ end
754
+ end
755
+
756
+ class BigRecordFixture #:nodoc:
757
+ include Enumerable
758
+
759
+ class BigRecordFixtureError < StandardError #:nodoc:
760
+ end
761
+
762
+ class BigRecordFixtureFormatError < BigRecordFixtureError #:nodoc:
763
+ end
764
+
765
+ attr_reader :class_name
766
+
767
+ def initialize(fixture, class_name)
768
+ case fixture
769
+ when Hash, YAML::Omap
770
+ @fixture = fixture
771
+ when String
772
+ @fixture = read_fixture_file(fixture)
773
+ else
774
+ raise ArgumentError, "Bad fixture argument #{fixture.inspect} during creation of #{class_name} fixture"
775
+ end
776
+
777
+ @class_name = class_name
778
+ end
779
+
780
+ def each
781
+ @fixture.each { |item| yield item }
782
+ end
783
+
784
+ def [](key)
785
+ @fixture[key]
786
+ end
787
+
788
+ def to_hash
789
+ @fixture
790
+ end
791
+
792
+ def key_list
793
+ columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
794
+ columns.join(", ")
795
+ end
796
+
797
+ def value_list
798
+ klass = @class_name.constantize rescue nil
799
+
800
+ list = @fixture.inject([]) do |fixtures, (key, value)|
801
+ col = klass.columns_hash[key] if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
802
+ fixtures << BigRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
803
+ end
804
+ list * ', '
805
+ end
806
+
807
+ def find
808
+ klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
809
+ if klass
810
+ klass.find(self[klass.primary_key])
811
+ else
812
+ raise BigRecordFixtureClassNotFound, "The class #{@class_name.inspect} was not found."
813
+ end
814
+ end
815
+
816
+ private
817
+ def read_fixture_file(fixture_file_path)
818
+ IO.readlines(fixture_file_path).inject({}) do |fixture, line|
819
+ # Mercifully skip empty lines.
820
+ next if line =~ /^\s*$/
821
+
822
+ # Use the same regular expression for attributes as Active Record.
823
+ unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
824
+ raise FormatError, "#{fixture_file_path}: fixture format error at '#{line}'. Expecting 'key => value'."
825
+ end
826
+ key, value = md.captures
827
+
828
+ # Disallow duplicate keys to catch typos.
829
+ raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
830
+ fixture[key] = value.strip
831
+ fixture
832
+ end
833
+ end
834
+ end
835
+
836
+ module BigRecord
837
+ module TestFixtures
838
+ def self.included(base)
839
+ base.class_eval do
840
+ setup :setup_bigrecord_fixtures
841
+ teardown :teardown_bigrecord_fixtures
842
+
843
+ superclass_delegating_accessor :bigrecord_fixture_table_names
844
+ superclass_delegating_accessor :bigrecord_fixture_class_names
845
+ superclass_delegating_accessor :bigrecord_use_transactional_fixtures
846
+ superclass_delegating_accessor :bigrecord_use_instantiated_fixtures # true, false, or :no_instances
847
+ superclass_delegating_accessor :bigrecord_pre_loaded_fixtures
848
+
849
+ self.bigrecord_fixture_table_names = []
850
+ self.bigrecord_use_transactional_fixtures = false
851
+ self.bigrecord_use_instantiated_fixtures = true
852
+ self.bigrecord_pre_loaded_fixtures = false
853
+
854
+ @@already_loaded_bigrecord_fixtures = {}
855
+ self.bigrecord_fixture_class_names = {}
856
+ end
857
+
858
+ base.extend ClassMethods
859
+ end
860
+
861
+ module ClassMethods
862
+
863
+ def bigrecord_set_fixture_class(class_names = {})
864
+ self.bigrecord_fixture_class_names = self.bigrecord_fixture_class_names.merge(class_names)
865
+ end
866
+
867
+ def bigrecord_fixtures(*bigrecord_table_names)
868
+ if bigrecord_table_names.first == :all
869
+ bigrecord_table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
870
+ bigrecord_table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
871
+ else
872
+ bigrecord_table_names = bigrecord_table_names.flatten.map { |n| n.to_s }
873
+ end
874
+
875
+ self.bigrecord_fixture_table_names |= bigrecord_table_names
876
+ require_bigrecord_fixture_classes(bigrecord_table_names)
877
+ setup_bigrecord_fixture_accessors(bigrecord_table_names)
878
+ end
879
+
880
+ def require_bigrecord_fixture_classes(table_names = nil)
881
+ (table_names || fixture_table_names).each do |table_name|
882
+ file_name = table_name.to_s
883
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
884
+ begin
885
+ require_dependency file_name
886
+ rescue LoadError
887
+ # Let's hope the developer has included it himself
888
+ end
889
+ end
890
+ end
891
+
892
+ def setup_bigrecord_fixture_accessors(table_names = nil)
893
+ (table_names || fixture_table_names).each do |table_name|
894
+ table_name = table_name.to_s.tr('.', '_')
895
+
896
+ define_method(table_name) do |*fixtures|
897
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
898
+ @bigrecord_fixture_cache[table_name] ||= {}
899
+
900
+ instances = fixtures.map do |fixture|
901
+ @bigrecord_fixture_cache[table_name].delete(fixture) if force_reload
902
+
903
+ if @loaded_bigrecord_fixtures[table_name][fixture.to_s]
904
+ @bigrecord_fixture_cache[table_name][fixture] ||= @loaded_bigrecord_fixtures[table_name][fixture.to_s].find
905
+ else
906
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
907
+ end
908
+ end
909
+
910
+ instances.size == 1 ? instances.first : instances
911
+ end
912
+ end
913
+ end
914
+
915
+ def uses_transaction(*methods)
916
+ @bigrecord_uses_transaction = [] unless defined?(@bigrecord_uses_transaction)
917
+ @bigrecord_uses_transaction.concat methods.map(&:to_s)
918
+ end
919
+
920
+ def uses_transaction?(method)
921
+ @uses_transaction = [] unless defined?(@uses_transaction)
922
+ @uses_transaction.include?(method.to_s)
923
+ end
924
+ end
925
+
926
+ def run_in_transaction?
927
+ use_transactional_fixtures &&
928
+ !self.class.uses_transaction?(method_name)
929
+ end
930
+
931
+ def setup_bigrecord_fixtures
932
+ return if @bigrecord_fixtures_setup
933
+ @bigrecord_fixtures_setup = true
934
+ return unless defined?(BigRecord::Base) && !BigRecord::Base.configurations.blank?
935
+
936
+ if pre_loaded_fixtures && !use_transactional_fixtures
937
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
938
+ end
939
+
940
+ @bigrecord_fixture_cache = {}
941
+
942
+ BigRecordFixtures.reset_cache
943
+ @@already_loaded_bigrecord_fixtures[self.class] = nil
944
+ load_bigrecord_fixtures
945
+
946
+ # Instantiate fixtures for every test if requested.
947
+ instantiate_fixtures if use_instantiated_fixtures
948
+ end
949
+
950
+ def teardown_bigrecord_fixtures
951
+ return if @bigrecord_fixtures_teardown
952
+ @bigrecord_fixtures_teardown = true
953
+ return unless defined?(BigRecord::Base) && !BigRecord::Base.configurations.blank?
954
+
955
+ unless use_transactional_fixtures?
956
+ BigRecordFixtures.reset_cache
957
+ end
958
+
959
+ BigRecord::Base.verify_active_connections!
960
+ end
961
+
962
+ def self.method_added(method)
963
+ return if @__disable_method_added__
964
+ @__disable_method_added__ = true
965
+
966
+ case method.to_s
967
+ when 'setup'
968
+ undef_method :setup_without_fixtures if method_defined?(:setup_without_fixtures)
969
+ alias_method :setup_without_fixtures, :setup
970
+ define_method(:full_setup) do
971
+ setup_with_bigrecord_fixtures
972
+ setup_without_fixtures
973
+ end
974
+ alias_method :setup, :full_setup
975
+ when 'teardown'
976
+ undef_method :teardown_without_fixtures if method_defined?(:teardown_without_fixtures)
977
+ alias_method :teardown_without_fixtures, :teardown
978
+ define_method(:full_teardown) do
979
+ teardown_fixtures
980
+ teardown_with_bigrecord_fixtures
981
+ end
982
+ alias_method :teardown, :full_teardown
983
+ end
984
+
985
+ @__disable_method_added__ = false
986
+ end
987
+
988
+ private
989
+ def load_bigrecord_fixtures
990
+ @loaded_bigrecord_fixtures = {}
991
+ fixtures = BigRecordFixtures.create_fixtures(fixture_path, bigrecord_fixture_table_names, bigrecord_fixture_class_names)
992
+
993
+ unless fixtures.nil?
994
+ if fixtures.instance_of?(BigRecordFixtures)
995
+ @loaded_bigrecord_fixtures[fixtures.table_name] = fixtures
996
+ else
997
+ fixtures.each { |f| @loaded_bigrecord_fixtures[f.table_name] = f }
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
1003
+ @@required_bigrecord_fixture_classes = false
1004
+
1005
+ def instantiate_bigrecord_fixtures
1006
+ if pre_loaded_fixtures
1007
+ raise RuntimeError, 'Load fixtures before instantiating them.' if BigRecordFixtures.all_loaded_fixtures.empty?
1008
+ unless @@required_bigrecord_fixture_classes
1009
+ self.class.require_fixture_classes BigRecordFixtures.all_loaded_fixtures.keys
1010
+ @@required_bigrecord_fixture_classes = true
1011
+ end
1012
+ BigRecordFixtures.instantiate_all_loaded_fixtures(self, load_instances?)
1013
+ else
1014
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_bigrecord_fixtures.nil?
1015
+ @loaded_bigrecord_fixtures.each do |table_name, fixtures|
1016
+ BigRecordFixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ def load_bigrecord_instances?
1022
+ use_instantiated_fixtures != :no_instances
1023
+ end
1024
+ end
1025
+ end