activerecord 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,115 @@
1
+ module ActiveRecord
2
+ # = Active Record Counter Cache
3
+ module CounterCache
4
+ # Resets one or more counter caches to their correct value using an SQL
5
+ # count query. This is useful when adding new counter caches, or if the
6
+ # counter has been corrupted or modified directly by SQL.
7
+ #
8
+ # ==== Parameters
9
+ #
10
+ # * +id+ - The id of the object you wish to reset a counter on.
11
+ # * +counters+ - One or more counter names to reset
12
+ #
13
+ # ==== Examples
14
+ #
15
+ # # For Post with id #1 records reset the comments_count
16
+ # Post.reset_counters(1, :comments)
17
+ def reset_counters(id, *counters)
18
+ object = find(id)
19
+ counters.each do |association|
20
+ has_many_association = reflect_on_association(association.to_sym)
21
+
22
+ expected_name = if has_many_association.options[:as]
23
+ has_many_association.options[:as].to_s.classify
24
+ else
25
+ self.name
26
+ end
27
+
28
+ child_class = has_many_association.klass
29
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
30
+ reflection = belongs_to.find { |e| e.class_name == expected_name }
31
+ counter_name = reflection.counter_cache_column
32
+
33
+ self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
34
+ arel_table[counter_name] => object.send(association).count
35
+ })
36
+ end
37
+ return true
38
+ end
39
+
40
+ # A generic "counter updater" implementation, intended primarily to be
41
+ # used by increment_counter and decrement_counter, but which may also
42
+ # be useful on its own. It simply does a direct SQL update for the record
43
+ # with the given ID, altering the given hash of counters by the amount
44
+ # given by the corresponding value:
45
+ #
46
+ # ==== Parameters
47
+ #
48
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
49
+ # * +counters+ - An Array of Hashes containing the names of the fields
50
+ # to update as keys and the amount to update the field by as values.
51
+ #
52
+ # ==== Examples
53
+ #
54
+ # # For the Post with id of 5, decrement the comment_count by 1, and
55
+ # # increment the action_count by 1
56
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
57
+ # # Executes the following SQL:
58
+ # # UPDATE posts
59
+ # # SET comment_count = comment_count - 1,
60
+ # # action_count = action_count + 1
61
+ # # WHERE id = 5
62
+ #
63
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
64
+ # Post.update_counters [10, 15], :comment_count => 1
65
+ # # Executes the following SQL:
66
+ # # UPDATE posts
67
+ # # SET comment_count = comment_count + 1,
68
+ # # WHERE id IN (10, 15)
69
+ def update_counters(id, counters)
70
+ updates = counters.map do |counter_name, value|
71
+ operator = value < 0 ? '-' : '+'
72
+ quoted_column = connection.quote_column_name(counter_name)
73
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
74
+ end
75
+
76
+ update_all(updates.join(', '), primary_key => id )
77
+ end
78
+
79
+ # Increment a number field by one, usually representing a count.
80
+ #
81
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
82
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
83
+ # shown it would have to run an SQL query to find how many posts and comments there are.
84
+ #
85
+ # ==== Parameters
86
+ #
87
+ # * +counter_name+ - The name of the field that should be incremented.
88
+ # * +id+ - The id of the object that should be incremented.
89
+ #
90
+ # ==== Examples
91
+ #
92
+ # # Increment the post_count column for the record with an id of 5
93
+ # DiscussionBoard.increment_counter(:post_count, 5)
94
+ def increment_counter(counter_name, id)
95
+ update_counters(id, counter_name => 1)
96
+ end
97
+
98
+ # Decrement a number field by one, usually representing a count.
99
+ #
100
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
101
+ #
102
+ # ==== Parameters
103
+ #
104
+ # * +counter_name+ - The name of the field that should be decremented.
105
+ # * +id+ - The id of the object that should be decremented.
106
+ #
107
+ # ==== Examples
108
+ #
109
+ # # Decrement the post_count column for the record with an id of 5
110
+ # DiscussionBoard.decrement_counter(:post_count, 5)
111
+ def decrement_counter(counter_name, id)
112
+ update_counters(id, counter_name => -1)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveRecord
2
+
3
+ # = Active Record Dynamic Finder Match
4
+ #
5
+ # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
6
+ #
7
+ class DynamicFinderMatch
8
+ def self.match(method)
9
+ df_match = self.new(method)
10
+ df_match.finder ? df_match : nil
11
+ end
12
+
13
+ def initialize(method)
14
+ @finder = :first
15
+ @bang = false
16
+ @instantiator = nil
17
+
18
+ case method.to_s
19
+ when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
20
+ @finder = :last if $1 == 'last_by'
21
+ @finder = :all if $1 == 'all_by'
22
+ names = $2
23
+ when /^find_by_([_a-zA-Z]\w*)\!$/
24
+ @bang = true
25
+ names = $1
26
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
27
+ @instantiator = $1 == 'initialize' ? :new : :create
28
+ names = $2
29
+ else
30
+ @finder = nil
31
+ end
32
+ @attribute_names = names && names.split('_and_')
33
+ end
34
+
35
+ attr_reader :finder, :attribute_names, :instantiator
36
+
37
+ def finder?
38
+ !@finder.nil? && @instantiator.nil?
39
+ end
40
+
41
+ def instantiator?
42
+ @finder == :first && !@instantiator.nil?
43
+ end
44
+
45
+ def creator?
46
+ @finder == :first && @instantiator == :create
47
+ end
48
+
49
+ def bang?
50
+ @bang
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+
3
+ # = Active Record Dynamic Scope Match
4
+ #
5
+ # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
6
+ # if, for example, the <tt>Product</tt> has an attribute with that name. You can
7
+ # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
8
+ # scope except that it's dynamic.
9
+ class DynamicScopeMatch
10
+ def self.match(method)
11
+ ds_match = self.new(method)
12
+ ds_match.scope ? ds_match : nil
13
+ end
14
+
15
+ def initialize(method)
16
+ @scope = true
17
+ case method.to_s
18
+ when /^scoped_by_([_a-zA-Z]\w*)$/
19
+ names = $1
20
+ else
21
+ @scope = nil
22
+ end
23
+ @attribute_names = names && names.split('_and_')
24
+ end
25
+
26
+ attr_reader :scope, :attribute_names
27
+
28
+ def scope?
29
+ !@scope.nil?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,172 @@
1
+ module ActiveRecord
2
+
3
+ # = Active Record Errors
4
+ #
5
+ # Generic Active Record exception class.
6
+ class ActiveRecordError < StandardError
7
+ end
8
+
9
+ # Raised when the single-table inheritance mechanism fails to locate the subclass
10
+ # (for example due to improper usage of column that +inheritance_column+ points to).
11
+ class SubclassNotFound < ActiveRecordError #:nodoc:
12
+ end
13
+
14
+ # Raised when an object assigned to an association has an incorrect type.
15
+ #
16
+ # class Ticket < ActiveRecord::Base
17
+ # has_many :patches
18
+ # end
19
+ #
20
+ # class Patch < ActiveRecord::Base
21
+ # belongs_to :ticket
22
+ # end
23
+ #
24
+ # # Comments are not patches, this assignment raises AssociationTypeMismatch.
25
+ # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
26
+ class AssociationTypeMismatch < ActiveRecordError
27
+ end
28
+
29
+ # Raised when unserialized object's type mismatches one specified for serializable field.
30
+ class SerializationTypeMismatch < ActiveRecordError
31
+ end
32
+
33
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
34
+ # misses adapter field).
35
+ class AdapterNotSpecified < ActiveRecordError
36
+ end
37
+
38
+ # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
39
+ class AdapterNotFound < ActiveRecordError
40
+ end
41
+
42
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
43
+ # is given a nil object).
44
+ class ConnectionNotEstablished < ActiveRecordError
45
+ end
46
+
47
+ # Raised when Active Record cannot find record by given id or set of ids.
48
+ class RecordNotFound < ActiveRecordError
49
+ end
50
+
51
+ # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
52
+ # saved because record is invalid.
53
+ class RecordNotSaved < ActiveRecordError
54
+ end
55
+
56
+ # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
57
+ # MySQL when Ruby driver used is too old).
58
+ class StatementInvalid < ActiveRecordError
59
+ end
60
+
61
+ # Raised when SQL statement is invalid and the application gets a blank result.
62
+ class ThrowResult < ActiveRecordError
63
+ end
64
+
65
+ # Parent class for all specific exceptions which wrap database driver exceptions
66
+ # provides access to the original exception also.
67
+ class WrappedDatabaseException < StatementInvalid
68
+ attr_reader :original_exception
69
+
70
+ def initialize(message, original_exception)
71
+ super(message)
72
+ @original_exception = original_exception
73
+ end
74
+ end
75
+
76
+ # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
77
+ class RecordNotUnique < WrappedDatabaseException
78
+ end
79
+
80
+ # Raised when a record cannot be inserted or updated because it references a non-existent record.
81
+ class InvalidForeignKey < WrappedDatabaseException
82
+ end
83
+
84
+ # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
85
+ # when using +find+ method)
86
+ # does not match number of expected variables.
87
+ #
88
+ # For example, in
89
+ #
90
+ # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
91
+ #
92
+ # two placeholders are given but only one variable to fill them.
93
+ class PreparedStatementInvalid < ActiveRecordError
94
+ end
95
+
96
+ # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
97
+ # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
98
+ # the page before the other.
99
+ #
100
+ # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
101
+ class StaleObjectError < ActiveRecordError
102
+ end
103
+
104
+ # Raised when association is being configured improperly or
105
+ # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
106
+ class ConfigurationError < ActiveRecordError
107
+ end
108
+
109
+ # Raised on attempt to update record that is instantiated as read only.
110
+ class ReadOnlyRecord < ActiveRecordError
111
+ end
112
+
113
+ # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
114
+ # to distinguish a deliberate rollback from other exceptional situations.
115
+ # Normally, raising an exception will cause the +transaction+ method to rollback
116
+ # the database transaction *and* pass on the exception. But if you raise an
117
+ # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
118
+ # without passing on the exception.
119
+ #
120
+ # For example, you could do this in your controller to rollback a transaction:
121
+ #
122
+ # class BooksController < ActionController::Base
123
+ # def create
124
+ # Book.transaction do
125
+ # book = Book.new(params[:book])
126
+ # book.save!
127
+ # if today_is_friday?
128
+ # # The system must fail on Friday so that our support department
129
+ # # won't be out of job. We silently rollback this transaction
130
+ # # without telling the user.
131
+ # raise ActiveRecord::Rollback, "Call tech support!"
132
+ # end
133
+ # end
134
+ # # ActiveRecord::Rollback is the only exception that won't be passed on
135
+ # # by ActiveRecord::Base.transaction, so this line will still be reached
136
+ # # even on Friday.
137
+ # redirect_to root_url
138
+ # end
139
+ # end
140
+ class Rollback < ActiveRecordError
141
+ end
142
+
143
+ # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
144
+ class DangerousAttributeError < ActiveRecordError
145
+ end
146
+
147
+ # Raised when unknown attributes are supplied via mass assignment.
148
+ class UnknownAttributeError < NoMethodError
149
+ end
150
+
151
+ # Raised when an error occurred while doing a mass assignment to an attribute through the
152
+ # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
153
+ # offending attribute.
154
+ class AttributeAssignmentError < ActiveRecordError
155
+ attr_reader :exception, :attribute
156
+ def initialize(message, exception, attribute)
157
+ @exception = exception
158
+ @attribute = attribute
159
+ @message = message
160
+ end
161
+ end
162
+
163
+ # Raised when there are multiple errors while doing a mass assignment through the +attributes+
164
+ # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
165
+ # objects, each corresponding to the error while assigning to an attribute.
166
+ class MultiparameterAssignmentErrors < ActiveRecordError
167
+ attr_reader :errors
168
+ def initialize(errors)
169
+ @errors = errors
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,1008 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+ require 'csv'
4
+ require 'zlib'
5
+ require 'active_support/dependencies'
6
+ require 'active_support/core_ext/array/wrap'
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/logger'
9
+
10
+ if RUBY_VERSION < '1.9'
11
+ module YAML #:nodoc:
12
+ class Omap #:nodoc:
13
+ def keys; map { |k, v| k } end
14
+ def values; map { |k, v| v } end
15
+ end
16
+ end
17
+ end
18
+
19
+ if defined? ActiveRecord
20
+ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
21
+ end
22
+ else
23
+ class FixtureClassNotFound < StandardError #:nodoc:
24
+ end
25
+ end
26
+
27
+ class FixturesFileNotFound < StandardError; end
28
+
29
+ # Fixtures are a way of organizing data that you want to test against; in short, sample data.
30
+ #
31
+ # = Fixture formats
32
+ #
33
+ # Fixtures come in 3 flavors:
34
+ #
35
+ # 1. YAML fixtures
36
+ # 2. CSV fixtures
37
+ # 3. Single-file fixtures
38
+ #
39
+ # == YAML fixtures
40
+ #
41
+ # This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
42
+ # in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
43
+ #
44
+ # Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed
45
+ # in the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is
46
+ # automatically configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
47
+ # The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
48
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
49
+ #
50
+ # rubyonrails:
51
+ # id: 1
52
+ # name: Ruby on Rails
53
+ # url: http://www.rubyonrails.org
54
+ #
55
+ # google:
56
+ # id: 2
57
+ # name: Google
58
+ # url: http://www.google.com
59
+ #
60
+ # This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
61
+ # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
62
+ # pleasure.
63
+ #
64
+ # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
65
+ # See http://yaml.org/type/omap.html
66
+ # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
67
+ # This is commonly needed for tree structures. Example:
68
+ #
69
+ # --- !omap
70
+ # - parent:
71
+ # id: 1
72
+ # parent_id: NULL
73
+ # title: Parent
74
+ # - child:
75
+ # id: 2
76
+ # parent_id: 1
77
+ # title: Child
78
+ #
79
+ # == CSV fixtures
80
+ #
81
+ # Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored
82
+ # in a single file, but instead end with the <tt>.csv</tt> file extension
83
+ # (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
84
+ #
85
+ # The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
86
+ # humans. The first line of the CSV file is a comma-separated list of field names. The rest of the
87
+ # file is then comprised
88
+ # of the actual data (1 per line). Here's an example:
89
+ #
90
+ # id, name, url
91
+ # 1, Ruby On Rails, http://www.rubyonrails.org
92
+ # 2, Google, http://www.google.com
93
+ #
94
+ # Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
95
+ # need to use a double quote character, you must escape it with another double quote.
96
+ #
97
+ # Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
98
+ # fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
99
+ # number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
100
+ # "web_site_2".
101
+ #
102
+ # Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
103
+ # have existing data somewhere already.
104
+ #
105
+ # == Single-file fixtures
106
+ #
107
+ # This type of fixture was the original format for Active Record that has since been deprecated in
108
+ # favor of the YAML and CSV formats.
109
+ # Fixtures for this format are created by placing text files in a sub-directory (with the name of the model)
110
+ # to the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
111
+ # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
112
+ # like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
113
+ #
114
+ # Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
115
+ # extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension.
116
+ # Here's what the above example might look like:
117
+ #
118
+ # web_sites/google
119
+ # web_sites/yahoo.txt
120
+ # web_sites/ruby-on-rails
121
+ #
122
+ # The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
123
+ # of "name => value". Here's an example of the ruby-on-rails fixture above:
124
+ #
125
+ # id => 1
126
+ # name => Ruby on Rails
127
+ # url => http://www.rubyonrails.org
128
+ #
129
+ # = Using fixtures in testcases
130
+ #
131
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
132
+ # fixtures, but first let's take a look at a sample unit test:
133
+ #
134
+ # require 'test_helper'
135
+ #
136
+ # class WebSiteTest < ActiveSupport::TestCase
137
+ # test "web_site_count" do
138
+ # assert_equal 2, WebSite.count
139
+ # end
140
+ # end
141
+ #
142
+ # By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database,
143
+ # so this test will succeed.
144
+ # The testing environment will automatically load the all fixtures into the database before each test.
145
+ # To ensure consistent data, the environment deletes the fixtures before running the load.
146
+ #
147
+ # In addition to being available in the database, the fixture's data may also be accessed by
148
+ # using a special dynamic method, which has the same name as the model, and accepts the
149
+ # name of the fixture to instantiate:
150
+ #
151
+ # test "find" do
152
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
153
+ # end
154
+ #
155
+ # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the following tests:
156
+ #
157
+ # test "find_alt_method_1" do
158
+ # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
159
+ # end
160
+ #
161
+ # test "find_alt_method_2" do
162
+ # assert_equal "Ruby on Rails", @rubyonrails.news
163
+ # end
164
+ #
165
+ # In order to use these methods to access fixtured data within your testcases, you must specify one of the
166
+ # following in your <tt>ActiveSupport::TestCase</tt>-derived class:
167
+ #
168
+ # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
169
+ # self.use_instantiated_fixtures = true
170
+ #
171
+ # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
172
+ # self.use_instantiated_fixtures = :no_instances
173
+ #
174
+ # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
175
+ # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
176
+ # large sets of fixtured data.
177
+ #
178
+ # = Dynamic fixtures with ERb
179
+ #
180
+ # 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
181
+ # mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
182
+ #
183
+ # <% for i in 1..1000 %>
184
+ # fix_<%= i %>:
185
+ # id: <%= i %>
186
+ # name: guy_<%= 1 %>
187
+ # <% end %>
188
+ #
189
+ # This will create 1000 very simple YAML fixtures.
190
+ #
191
+ # Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
192
+ # This is however a feature to be used with some caution. The point of fixtures are that they're
193
+ # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
194
+ # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
195
+ # in fixtures are to be considered a code smell.
196
+ #
197
+ # = Transactional fixtures
198
+ #
199
+ # TestCases can use begin+rollback to isolate their changes to the database instead of having to
200
+ # 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
218
+ # and every case rolls back its changes.
219
+ #
220
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
221
+ # access to fixture data for every table that has been loaded through fixtures (depending on the
222
+ # value of +use_instantiated_fixtures+)
223
+ #
224
+ # When *not* to use transactional fixtures:
225
+ #
226
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
227
+ # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
228
+ # and rolled back in teardown. Thus, you won't be able to verify
229
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
230
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
231
+ # Use InnoDB, MaxDB, or NDB instead.
232
+ #
233
+ # = Advanced YAML Fixtures
234
+ #
235
+ # YAML fixtures that don't specify an ID get some extra features:
236
+ #
237
+ # * Stable, autogenerated IDs
238
+ # * Label references for associations (belongs_to, has_one, has_many)
239
+ # * HABTM associations as inline lists
240
+ # * Autofilled timestamp columns
241
+ # * Fixture label interpolation
242
+ # * Support for YAML defaults
243
+ #
244
+ # == Stable, autogenerated IDs
245
+ #
246
+ # Here, have a monkey fixture:
247
+ #
248
+ # george:
249
+ # id: 1
250
+ # name: George the Monkey
251
+ #
252
+ # reginald:
253
+ # id: 2
254
+ # name: Reginald the Pirate
255
+ #
256
+ # Each of these fixtures has two unique identifiers: one for the database
257
+ # and one for the humans. Why don't we generate the primary key instead?
258
+ # Hashing each fixture's label yields a consistent ID:
259
+ #
260
+ # george: # generated id: 503576764
261
+ # name: George the Monkey
262
+ #
263
+ # reginald: # generated id: 324201669
264
+ # name: Reginald the Pirate
265
+ #
266
+ # Active Record looks at the fixture's model class, discovers the correct
267
+ # primary key, and generates it right before inserting the fixture
268
+ # into the database.
269
+ #
270
+ # The generated ID for a given label is constant, so we can discover
271
+ # any fixture's ID without loading anything, as long as we know the label.
272
+ #
273
+ # == Label references for associations (belongs_to, has_one, has_many)
274
+ #
275
+ # Specifying foreign keys in fixtures can be very fragile, not to
276
+ # mention difficult to read. Since Active Record can figure out the ID of
277
+ # any fixture from its label, you can specify FK's by label instead of ID.
278
+ #
279
+ # === belongs_to
280
+ #
281
+ # Let's break out some more monkeys and pirates.
282
+ #
283
+ # ### in pirates.yml
284
+ #
285
+ # reginald:
286
+ # id: 1
287
+ # name: Reginald the Pirate
288
+ # monkey_id: 1
289
+ #
290
+ # ### in monkeys.yml
291
+ #
292
+ # george:
293
+ # id: 1
294
+ # name: George the Monkey
295
+ # pirate_id: 1
296
+ #
297
+ # Add a few more monkeys and pirates and break this into multiple files,
298
+ # and it gets pretty hard to keep track of what's going on. Let's
299
+ # use labels instead of IDs:
300
+ #
301
+ # ### in pirates.yml
302
+ #
303
+ # reginald:
304
+ # name: Reginald the Pirate
305
+ # monkey: george
306
+ #
307
+ # ### in monkeys.yml
308
+ #
309
+ # george:
310
+ # name: George the Monkey
311
+ # pirate: reginald
312
+ #
313
+ # Pow! All is made clear. Active Record reflects on the fixture's model class,
314
+ # finds all the +belongs_to+ associations, and allows you to specify
315
+ # a target *label* for the *association* (monkey: george) rather than
316
+ # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
317
+ #
318
+ # ==== Polymorphic belongs_to
319
+ #
320
+ # Supporting polymorphic relationships is a little bit more complicated, since
321
+ # Active Record needs to know what type your association is pointing at. Something
322
+ # like this should look familiar:
323
+ #
324
+ # ### in fruit.rb
325
+ #
326
+ # belongs_to :eater, :polymorphic => true
327
+ #
328
+ # ### in fruits.yml
329
+ #
330
+ # apple:
331
+ # id: 1
332
+ # name: apple
333
+ # eater_id: 1
334
+ # eater_type: Monkey
335
+ #
336
+ # Can we do better? You bet!
337
+ #
338
+ # apple:
339
+ # eater: george (Monkey)
340
+ #
341
+ # Just provide the polymorphic target type and Active Record will take care of the rest.
342
+ #
343
+ # === has_and_belongs_to_many
344
+ #
345
+ # Time to give our monkey some fruit.
346
+ #
347
+ # ### in monkeys.yml
348
+ #
349
+ # george:
350
+ # id: 1
351
+ # name: George the Monkey
352
+ #
353
+ # ### in fruits.yml
354
+ #
355
+ # apple:
356
+ # id: 1
357
+ # name: apple
358
+ #
359
+ # orange:
360
+ # id: 2
361
+ # name: orange
362
+ #
363
+ # grape:
364
+ # id: 3
365
+ # name: grape
366
+ #
367
+ # ### in fruits_monkeys.yml
368
+ #
369
+ # apple_george:
370
+ # fruit_id: 1
371
+ # monkey_id: 1
372
+ #
373
+ # orange_george:
374
+ # fruit_id: 2
375
+ # monkey_id: 1
376
+ #
377
+ # grape_george:
378
+ # fruit_id: 3
379
+ # monkey_id: 1
380
+ #
381
+ # Let's make the HABTM fixture go away.
382
+ #
383
+ # ### in monkeys.yml
384
+ #
385
+ # george:
386
+ # id: 1
387
+ # name: George the Monkey
388
+ # fruits: apple, orange, grape
389
+ #
390
+ # ### in fruits.yml
391
+ #
392
+ # apple:
393
+ # name: apple
394
+ #
395
+ # orange:
396
+ # name: orange
397
+ #
398
+ # grape:
399
+ # name: grape
400
+ #
401
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
402
+ # on George's fixture, but we could've just as easily specified a list
403
+ # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
404
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
405
+ # associations.
406
+ #
407
+ # == Autofilled timestamp columns
408
+ #
409
+ # If your table/model specifies any of Active Record's
410
+ # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
411
+ # they will automatically be set to <tt>Time.now</tt>.
412
+ #
413
+ # If you've set specific values, they'll be left alone.
414
+ #
415
+ # == Fixture label interpolation
416
+ #
417
+ # The label of the current fixture is always available as a column value:
418
+ #
419
+ # geeksomnia:
420
+ # name: Geeksomnia's Account
421
+ # subdomain: $LABEL
422
+ #
423
+ # Also, sometimes (like when porting older join table fixtures) you'll need
424
+ # to be able to get a hold of the identifier for a given label. ERB
425
+ # to the rescue:
426
+ #
427
+ # george_reginald:
428
+ # monkey_id: <%= Fixtures.identify(:reginald) %>
429
+ # pirate_id: <%= Fixtures.identify(:george) %>
430
+ #
431
+ # == Support for YAML defaults
432
+ #
433
+ # You probably already know how to use YAML to set and reuse defaults in
434
+ # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
435
+ #
436
+ # DEFAULTS: &DEFAULTS
437
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
438
+ #
439
+ # first:
440
+ # name: Smurf
441
+ # <<: *DEFAULTS
442
+ #
443
+ # second:
444
+ # name: Fraggle
445
+ # <<: *DEFAULTS
446
+ #
447
+ # Any fixture labeled "DEFAULTS" is safely ignored.
448
+
449
+ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
450
+ MAX_ID = 2 ** 30 - 1
451
+ DEFAULT_FILTER_RE = /\.ya?ml$/
452
+
453
+ @@all_cached_fixtures = {}
454
+
455
+ def self.reset_cache(connection = nil)
456
+ connection ||= ActiveRecord::Base.connection
457
+ @@all_cached_fixtures[connection.object_id] = {}
458
+ end
459
+
460
+ def self.cache_for_connection(connection)
461
+ @@all_cached_fixtures[connection.object_id] ||= {}
462
+ @@all_cached_fixtures[connection.object_id]
463
+ end
464
+
465
+ def self.fixture_is_cached?(connection, table_name)
466
+ cache_for_connection(connection)[table_name]
467
+ end
468
+
469
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
470
+ if keys_to_fetch
471
+ fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
472
+ else
473
+ fixtures = cache_for_connection(connection).values
474
+ end
475
+ fixtures.size > 1 ? fixtures : fixtures.first
476
+ end
477
+
478
+ def self.cache_fixtures(connection, fixtures_map)
479
+ cache_for_connection(connection).update(fixtures_map)
480
+ end
481
+
482
+ def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
483
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
484
+ if load_instances
485
+ ActiveRecord::Base.silence do
486
+ fixtures.each do |name, fixture|
487
+ begin
488
+ object.instance_variable_set "@#{name}", fixture.find
489
+ rescue FixtureClassNotFound
490
+ nil
491
+ end
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
498
+ all_loaded_fixtures.each do |table_name, fixtures|
499
+ Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
500
+ end
501
+ end
502
+
503
+ cattr_accessor :all_loaded_fixtures
504
+ self.all_loaded_fixtures = {}
505
+
506
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
507
+ table_names = [table_names].flatten.map { |n| n.to_s }
508
+ table_names.each { |n| class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') }
509
+ connection = block_given? ? yield : ActiveRecord::Base.connection
510
+
511
+ table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
512
+
513
+ unless table_names_to_fetch.empty?
514
+ ActiveRecord::Base.silence do
515
+ connection.disable_referential_integrity do
516
+ fixtures_map = {}
517
+
518
+ fixtures = table_names_to_fetch.map do |table_name|
519
+ fixtures_map[table_name] = Fixtures.new(connection, table_name.tr('/', '_'), class_names[table_name.tr('/', '_').to_sym], File.join(fixtures_directory, table_name))
520
+ end
521
+
522
+ all_loaded_fixtures.update(fixtures_map)
523
+
524
+ connection.transaction(:requires_new => true) do
525
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
526
+ fixtures.each { |fixture| fixture.insert_fixtures }
527
+
528
+ # Cap primary key sequences to max(pk).
529
+ if connection.respond_to?(:reset_pk_sequence!)
530
+ table_names.each do |table_name|
531
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
532
+ end
533
+ end
534
+ end
535
+
536
+ cache_fixtures(connection, fixtures_map)
537
+ end
538
+ end
539
+ end
540
+ cached_fixtures(connection, table_names)
541
+ end
542
+
543
+ # Returns a consistent, platform-independent identifier for +label+.
544
+ # Identifiers are positive integers less than 2^32.
545
+ def self.identify(label)
546
+ Zlib.crc32(label.to_s) % MAX_ID
547
+ end
548
+
549
+ attr_reader :table_name, :name
550
+
551
+ def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
552
+ @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
553
+ @name = table_name # preserve fixture base name
554
+ @class_name = class_name ||
555
+ (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
556
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
557
+ @table_name = class_name.table_name if class_name.respond_to?(:table_name)
558
+ @connection = class_name.connection if class_name.respond_to?(:connection)
559
+ read_fixture_files
560
+ end
561
+
562
+ def delete_existing_fixtures
563
+ @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
564
+ end
565
+
566
+ def insert_fixtures
567
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
568
+ now = now.to_s(:db)
569
+
570
+ # allow a standard key to be used for doing defaults in YAML
571
+ if is_a?(Hash)
572
+ delete('DEFAULTS')
573
+ else
574
+ delete(assoc('DEFAULTS'))
575
+ end
576
+
577
+ # track any join tables we need to insert later
578
+ habtm_fixtures = Hash.new do |h, habtm|
579
+ h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
580
+ end
581
+
582
+ each do |label, fixture|
583
+ row = fixture.to_hash
584
+
585
+ if model_class && model_class < ActiveRecord::Base
586
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
587
+ if model_class.record_timestamps
588
+ timestamp_column_names.each do |name|
589
+ row[name] = now unless row.key?(name)
590
+ end
591
+ end
592
+
593
+ # interpolate the fixture label
594
+ row.each do |key, value|
595
+ row[key] = label if value == "$LABEL"
596
+ end
597
+
598
+ # generate a primary key if necessary
599
+ if has_primary_key_column? && !row.include?(primary_key_name)
600
+ row[primary_key_name] = Fixtures.identify(label)
601
+ end
602
+
603
+ # If STI is used, find the correct subclass for association reflection
604
+ reflection_class =
605
+ if row.include?(inheritance_column_name)
606
+ row[inheritance_column_name].constantize rescue model_class
607
+ else
608
+ model_class
609
+ end
610
+
611
+ reflection_class.reflect_on_all_associations.each do |association|
612
+ case association.macro
613
+ when :belongs_to
614
+ # Do not replace association name with association foreign key if they are named the same
615
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
616
+
617
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
618
+ if association.options[:polymorphic]
619
+ if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
620
+ target_type = $1
621
+ target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
622
+
623
+ # support polymorphic belongs_to as "label (Type)"
624
+ row[target_type_name] = target_type
625
+ end
626
+ end
627
+
628
+ row[fk_name] = Fixtures.identify(value)
629
+ end
630
+ when :has_and_belongs_to_many
631
+ if (targets = row.delete(association.name.to_s))
632
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
633
+ join_fixtures = habtm_fixtures[association]
634
+
635
+ targets.each do |target|
636
+ join_fixtures["#{label}_#{target}"] = Fixture.new(
637
+ { association.primary_key_name => row[primary_key_name],
638
+ association.association_foreign_key => Fixtures.identify(target) },
639
+ nil, @connection)
640
+ end
641
+ end
642
+ end
643
+ end
644
+ end
645
+
646
+ @connection.insert_fixture(fixture, @table_name)
647
+ end
648
+
649
+ # insert any HABTM join tables we discovered
650
+ habtm_fixtures.values.each do |fixture|
651
+ fixture.delete_existing_fixtures
652
+ fixture.insert_fixtures
653
+ end
654
+ end
655
+
656
+ private
657
+ class HabtmFixtures < ::Fixtures #:nodoc:
658
+ def read_fixture_files; end
659
+ end
660
+
661
+ def model_class
662
+ unless defined?(@model_class)
663
+ @model_class =
664
+ if @class_name.nil? || @class_name.is_a?(Class)
665
+ @class_name
666
+ else
667
+ @class_name.constantize rescue nil
668
+ end
669
+ end
670
+
671
+ @model_class
672
+ end
673
+
674
+ def primary_key_name
675
+ @primary_key_name ||= model_class && model_class.primary_key
676
+ end
677
+
678
+ def has_primary_key_column?
679
+ @has_primary_key_column ||= primary_key_name &&
680
+ model_class.columns.any? { |c| c.name == primary_key_name }
681
+ end
682
+
683
+ def timestamp_column_names
684
+ @timestamp_column_names ||=
685
+ %w(created_at created_on updated_at updated_on) & column_names
686
+ end
687
+
688
+ def inheritance_column_name
689
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
690
+ end
691
+
692
+ def column_names
693
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
694
+ end
695
+
696
+ def read_fixture_files
697
+ if File.file?(yaml_file_path)
698
+ read_yaml_fixture_files
699
+ elsif File.file?(csv_file_path)
700
+ read_csv_fixture_files
701
+ else
702
+ raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
703
+ end
704
+ end
705
+
706
+ def read_yaml_fixture_files
707
+ yaml_string = ""
708
+ Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
709
+ yaml_string << IO.read(subfixture_path)
710
+ end
711
+ yaml_string << IO.read(yaml_file_path)
712
+
713
+ if yaml = parse_yaml_string(yaml_string)
714
+ # If the file is an ordered map, extract its children.
715
+ yaml_value =
716
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
717
+ yaml.value
718
+ else
719
+ [yaml]
720
+ end
721
+
722
+ yaml_value.each do |fixture|
723
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
724
+ fixture.each do |name, data|
725
+ unless data
726
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
727
+ end
728
+
729
+ self[name] = Fixture.new(data, model_class, @connection)
730
+ end
731
+ end
732
+ end
733
+ end
734
+
735
+ def read_csv_fixture_files
736
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
737
+ header = reader.shift
738
+ i = 0
739
+ reader.each do |row|
740
+ data = {}
741
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
742
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
743
+ end
744
+ end
745
+
746
+ def yaml_file_path
747
+ "#{@fixture_path}.yml"
748
+ end
749
+
750
+ def csv_file_path
751
+ @fixture_path + ".csv"
752
+ end
753
+
754
+ def yaml_fixtures_key(path)
755
+ File.basename(@fixture_path).split(".").first
756
+ end
757
+
758
+ def parse_yaml_string(fixture_content)
759
+ YAML::load(erb_render(fixture_content))
760
+ rescue => error
761
+ 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}"
762
+ end
763
+
764
+ def erb_render(fixture_content)
765
+ ERB.new(fixture_content).result
766
+ end
767
+ end
768
+
769
+ class Fixture #:nodoc:
770
+ include Enumerable
771
+
772
+ class FixtureError < StandardError #:nodoc:
773
+ end
774
+
775
+ class FormatError < FixtureError #:nodoc:
776
+ end
777
+
778
+ attr_reader :model_class
779
+
780
+ def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
781
+ @connection = connection
782
+ @fixture = fixture
783
+ @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
784
+ end
785
+
786
+ def class_name
787
+ @model_class.name if @model_class
788
+ end
789
+
790
+ def each
791
+ @fixture.each { |item| yield item }
792
+ end
793
+
794
+ def [](key)
795
+ @fixture[key]
796
+ end
797
+
798
+ def to_hash
799
+ @fixture
800
+ end
801
+
802
+ def key_list
803
+ @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ')
804
+ end
805
+
806
+ def value_list
807
+ cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {}
808
+ @fixture.map do |key, value|
809
+ @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
810
+ end.join(', ')
811
+ end
812
+
813
+ def find
814
+ if model_class
815
+ model_class.find(self[model_class.primary_key])
816
+ else
817
+ raise FixtureClassNotFound, "No class attached to find."
818
+ end
819
+ end
820
+ end
821
+
822
+ module ActiveRecord
823
+ module TestFixtures
824
+ extend ActiveSupport::Concern
825
+
826
+ included do
827
+ setup :setup_fixtures
828
+ teardown :teardown_fixtures
829
+
830
+ superclass_delegating_accessor :fixture_path
831
+ superclass_delegating_accessor :fixture_table_names
832
+ superclass_delegating_accessor :fixture_class_names
833
+ superclass_delegating_accessor :use_transactional_fixtures
834
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
835
+ superclass_delegating_accessor :pre_loaded_fixtures
836
+
837
+ self.fixture_table_names = []
838
+ self.use_transactional_fixtures = true
839
+ self.use_instantiated_fixtures = false
840
+ self.pre_loaded_fixtures = false
841
+
842
+ self.fixture_class_names = {}
843
+ end
844
+
845
+ module ClassMethods
846
+ def set_fixture_class(class_names = {})
847
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
848
+ end
849
+
850
+ def fixtures(*table_names)
851
+ if table_names.first == :all
852
+ table_names = Dir["#{fixture_path}/**/*.{yml,csv}"]
853
+ table_names.map! { |f| f[(fixture_path.size + 1)..-5] }
854
+ else
855
+ table_names = table_names.flatten.map { |n| n.to_s }
856
+ end
857
+
858
+ self.fixture_table_names |= table_names
859
+ require_fixture_classes(table_names)
860
+ setup_fixture_accessors(table_names)
861
+ end
862
+
863
+ def try_to_load_dependency(file_name)
864
+ require_dependency file_name
865
+ rescue LoadError => e
866
+ # Let's hope the developer has included it himself
867
+
868
+ # Let's warn in case this is a subdependency, otherwise
869
+ # subdependency error messages are totally cryptic
870
+ if ActiveRecord::Base.logger
871
+ ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
872
+ end
873
+ end
874
+
875
+ def require_fixture_classes(table_names = nil)
876
+ (table_names || fixture_table_names).each do |table_name|
877
+ file_name = table_name.to_s
878
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
879
+ try_to_load_dependency(file_name)
880
+ end
881
+ end
882
+
883
+ def setup_fixture_accessors(table_names = nil)
884
+ table_names = Array.wrap(table_names || fixture_table_names)
885
+ table_names.each do |table_name|
886
+ table_name = table_name.to_s.tr('./', '_')
887
+
888
+ redefine_method(table_name) do |*fixtures|
889
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
890
+
891
+ @fixture_cache[table_name] ||= {}
892
+
893
+ instances = fixtures.map do |fixture|
894
+ @fixture_cache[table_name].delete(fixture) if force_reload
895
+
896
+ if @loaded_fixtures[table_name][fixture.to_s]
897
+ @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
898
+ else
899
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
900
+ end
901
+ end
902
+
903
+ instances.size == 1 ? instances.first : instances
904
+ end
905
+ private table_name
906
+ end
907
+ end
908
+
909
+ def uses_transaction(*methods)
910
+ @uses_transaction = [] unless defined?(@uses_transaction)
911
+ @uses_transaction.concat methods.map { |m| m.to_s }
912
+ end
913
+
914
+ def uses_transaction?(method)
915
+ @uses_transaction = [] unless defined?(@uses_transaction)
916
+ @uses_transaction.include?(method.to_s)
917
+ end
918
+ end
919
+
920
+ def run_in_transaction?
921
+ use_transactional_fixtures &&
922
+ !self.class.uses_transaction?(method_name)
923
+ end
924
+
925
+ def setup_fixtures
926
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
927
+
928
+ if pre_loaded_fixtures && !use_transactional_fixtures
929
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
930
+ end
931
+
932
+ @fixture_cache = {}
933
+ @@already_loaded_fixtures ||= {}
934
+
935
+ # Load fixtures once and begin transaction.
936
+ if run_in_transaction?
937
+ if @@already_loaded_fixtures[self.class]
938
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
939
+ else
940
+ load_fixtures
941
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
942
+ end
943
+ ActiveRecord::Base.connection.increment_open_transactions
944
+ ActiveRecord::Base.connection.transaction_joinable = false
945
+ ActiveRecord::Base.connection.begin_db_transaction
946
+ # Load fixtures for every test.
947
+ else
948
+ Fixtures.reset_cache
949
+ @@already_loaded_fixtures[self.class] = nil
950
+ load_fixtures
951
+ end
952
+
953
+ # Instantiate fixtures for every test if requested.
954
+ instantiate_fixtures if use_instantiated_fixtures
955
+ end
956
+
957
+ def teardown_fixtures
958
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
959
+
960
+ unless run_in_transaction?
961
+ Fixtures.reset_cache
962
+ end
963
+
964
+ # Rollback changes if a transaction is active.
965
+ if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
966
+ ActiveRecord::Base.connection.rollback_db_transaction
967
+ ActiveRecord::Base.connection.decrement_open_transactions
968
+ end
969
+ ActiveRecord::Base.clear_active_connections!
970
+ end
971
+
972
+ private
973
+ def load_fixtures
974
+ @loaded_fixtures = {}
975
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
976
+ unless fixtures.nil?
977
+ if fixtures.instance_of?(Fixtures)
978
+ @loaded_fixtures[fixtures.name] = fixtures
979
+ else
980
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
981
+ end
982
+ end
983
+ end
984
+
985
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
986
+ @@required_fixture_classes = false
987
+
988
+ def instantiate_fixtures
989
+ if pre_loaded_fixtures
990
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
991
+ unless @@required_fixture_classes
992
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
993
+ @@required_fixture_classes = true
994
+ end
995
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
996
+ else
997
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
998
+ @loaded_fixtures.each do |table_name, fixtures|
999
+ Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
1000
+ end
1001
+ end
1002
+ end
1003
+
1004
+ def load_instances?
1005
+ use_instantiated_fixtures != :no_instances
1006
+ end
1007
+ end
1008
+ end