square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -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,56 @@
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
+ finder = :first
10
+ bang = false
11
+ instantiator = nil
12
+
13
+ case method.to_s
14
+ when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
15
+ finder = :last if $1 == 'last_'
16
+ finder = :all if $1 == 'all_'
17
+ names = $2
18
+ when /^find_by_([_a-zA-Z]\w*)\!$/
19
+ bang = true
20
+ names = $1
21
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
22
+ instantiator = $1 == 'initialize' ? :new : :create
23
+ names = $2
24
+ else
25
+ return nil
26
+ end
27
+
28
+ new(finder, instantiator, bang, names.split('_and_'))
29
+ end
30
+
31
+ def initialize(finder, instantiator, bang, attribute_names)
32
+ @finder = finder
33
+ @instantiator = instantiator
34
+ @bang = bang
35
+ @attribute_names = attribute_names
36
+ end
37
+
38
+ attr_reader :finder, :attribute_names, :instantiator
39
+
40
+ def finder?
41
+ @finder && !@instantiator
42
+ end
43
+
44
+ def instantiator?
45
+ @finder == :first && @instantiator
46
+ end
47
+
48
+ def creator?
49
+ @finder == :first && @instantiator == :create
50
+ end
51
+
52
+ def bang?
53
+ @bang
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
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
+ return unless method.to_s =~ /^scoped_by_([_a-zA-Z]\w*)$/
12
+ new(true, $1 && $1.split('_and_'))
13
+ end
14
+
15
+ def initialize(scope, attribute_names)
16
+ @scope = scope
17
+ @attribute_names = attribute_names
18
+ end
19
+
20
+ attr_reader :scope, :attribute_names
21
+ alias :scope? :scope
22
+ end
23
+ 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,1006 @@
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 = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
708
+ File.file?(f)
709
+ } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
710
+
711
+ if yaml = parse_yaml_string(yaml_string)
712
+ # If the file is an ordered map, extract its children.
713
+ yaml_value =
714
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
715
+ yaml.value
716
+ else
717
+ [yaml]
718
+ end
719
+
720
+ yaml_value.each do |fixture|
721
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
722
+ fixture.each do |name, data|
723
+ unless data
724
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
725
+ end
726
+
727
+ self[name] = Fixture.new(data, model_class, @connection)
728
+ end
729
+ end
730
+ end
731
+ end
732
+
733
+ def read_csv_fixture_files
734
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
735
+ header = reader.shift
736
+ i = 0
737
+ reader.each do |row|
738
+ data = {}
739
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
740
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
741
+ end
742
+ end
743
+
744
+ def yaml_file_path
745
+ "#{@fixture_path}.yml"
746
+ end
747
+
748
+ def csv_file_path
749
+ @fixture_path + ".csv"
750
+ end
751
+
752
+ def yaml_fixtures_key(path)
753
+ File.basename(@fixture_path).split(".").first
754
+ end
755
+
756
+ def parse_yaml_string(fixture_content)
757
+ YAML::load(erb_render(fixture_content))
758
+ rescue => error
759
+ 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}"
760
+ end
761
+
762
+ def erb_render(fixture_content)
763
+ ERB.new(fixture_content).result
764
+ end
765
+ end
766
+
767
+ class Fixture #:nodoc:
768
+ include Enumerable
769
+
770
+ class FixtureError < StandardError #:nodoc:
771
+ end
772
+
773
+ class FormatError < FixtureError #:nodoc:
774
+ end
775
+
776
+ attr_reader :model_class
777
+
778
+ def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
779
+ @connection = connection
780
+ @fixture = fixture
781
+ @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
782
+ end
783
+
784
+ def class_name
785
+ @model_class.name if @model_class
786
+ end
787
+
788
+ def each
789
+ @fixture.each { |item| yield item }
790
+ end
791
+
792
+ def [](key)
793
+ @fixture[key]
794
+ end
795
+
796
+ def to_hash
797
+ @fixture
798
+ end
799
+
800
+ def key_list
801
+ @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ')
802
+ end
803
+
804
+ def value_list
805
+ cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {}
806
+ @fixture.map do |key, value|
807
+ @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
808
+ end.join(', ')
809
+ end
810
+
811
+ def find
812
+ if model_class
813
+ model_class.find(self[model_class.primary_key])
814
+ else
815
+ raise FixtureClassNotFound, "No class attached to find."
816
+ end
817
+ end
818
+ end
819
+
820
+ module ActiveRecord
821
+ module TestFixtures
822
+ extend ActiveSupport::Concern
823
+
824
+ included do
825
+ setup :setup_fixtures
826
+ teardown :teardown_fixtures
827
+
828
+ superclass_delegating_accessor :fixture_path
829
+ superclass_delegating_accessor :fixture_table_names
830
+ superclass_delegating_accessor :fixture_class_names
831
+ superclass_delegating_accessor :use_transactional_fixtures
832
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
833
+ superclass_delegating_accessor :pre_loaded_fixtures
834
+
835
+ self.fixture_table_names = []
836
+ self.use_transactional_fixtures = true
837
+ self.use_instantiated_fixtures = false
838
+ self.pre_loaded_fixtures = false
839
+
840
+ self.fixture_class_names = {}
841
+ end
842
+
843
+ module ClassMethods
844
+ def set_fixture_class(class_names = {})
845
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
846
+ end
847
+
848
+ def fixtures(*table_names)
849
+ if table_names.first == :all
850
+ table_names = Dir["#{fixture_path}/**/*.{yml,csv}"]
851
+ table_names.map! { |f| f[(fixture_path.size + 1)..-5] }
852
+ else
853
+ table_names = table_names.flatten.map { |n| n.to_s }
854
+ end
855
+
856
+ self.fixture_table_names |= table_names
857
+ require_fixture_classes(table_names)
858
+ setup_fixture_accessors(table_names)
859
+ end
860
+
861
+ def try_to_load_dependency(file_name)
862
+ require_dependency file_name
863
+ rescue LoadError => e
864
+ # Let's hope the developer has included it himself
865
+
866
+ # Let's warn in case this is a subdependency, otherwise
867
+ # subdependency error messages are totally cryptic
868
+ if ActiveRecord::Base.logger
869
+ ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
870
+ end
871
+ end
872
+
873
+ def require_fixture_classes(table_names = nil)
874
+ (table_names || fixture_table_names).each do |table_name|
875
+ file_name = table_name.to_s
876
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
877
+ try_to_load_dependency(file_name)
878
+ end
879
+ end
880
+
881
+ def setup_fixture_accessors(table_names = nil)
882
+ table_names = Array.wrap(table_names || fixture_table_names)
883
+ table_names.each do |table_name|
884
+ table_name = table_name.to_s.tr('./', '_')
885
+
886
+ redefine_method(table_name) do |*fixtures|
887
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
888
+
889
+ @fixture_cache[table_name] ||= {}
890
+
891
+ instances = fixtures.map do |fixture|
892
+ @fixture_cache[table_name].delete(fixture) if force_reload
893
+
894
+ if @loaded_fixtures[table_name][fixture.to_s]
895
+ @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
896
+ else
897
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
898
+ end
899
+ end
900
+
901
+ instances.size == 1 ? instances.first : instances
902
+ end
903
+ private table_name
904
+ end
905
+ end
906
+
907
+ def uses_transaction(*methods)
908
+ @uses_transaction = [] unless defined?(@uses_transaction)
909
+ @uses_transaction.concat methods.map { |m| m.to_s }
910
+ end
911
+
912
+ def uses_transaction?(method)
913
+ @uses_transaction = [] unless defined?(@uses_transaction)
914
+ @uses_transaction.include?(method.to_s)
915
+ end
916
+ end
917
+
918
+ def run_in_transaction?
919
+ use_transactional_fixtures &&
920
+ !self.class.uses_transaction?(method_name)
921
+ end
922
+
923
+ def setup_fixtures
924
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
925
+
926
+ if pre_loaded_fixtures && !use_transactional_fixtures
927
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
928
+ end
929
+
930
+ @fixture_cache = {}
931
+ @@already_loaded_fixtures ||= {}
932
+
933
+ # Load fixtures once and begin transaction.
934
+ if run_in_transaction?
935
+ if @@already_loaded_fixtures[self.class]
936
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
937
+ else
938
+ load_fixtures
939
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
940
+ end
941
+ ActiveRecord::Base.connection.increment_open_transactions
942
+ ActiveRecord::Base.connection.transaction_joinable = false
943
+ ActiveRecord::Base.connection.begin_db_transaction
944
+ # Load fixtures for every test.
945
+ else
946
+ Fixtures.reset_cache
947
+ @@already_loaded_fixtures[self.class] = nil
948
+ load_fixtures
949
+ end
950
+
951
+ # Instantiate fixtures for every test if requested.
952
+ instantiate_fixtures if use_instantiated_fixtures
953
+ end
954
+
955
+ def teardown_fixtures
956
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
957
+
958
+ unless run_in_transaction?
959
+ Fixtures.reset_cache
960
+ end
961
+
962
+ # Rollback changes if a transaction is active.
963
+ if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
964
+ ActiveRecord::Base.connection.rollback_db_transaction
965
+ ActiveRecord::Base.connection.decrement_open_transactions
966
+ end
967
+ ActiveRecord::Base.clear_active_connections!
968
+ end
969
+
970
+ private
971
+ def load_fixtures
972
+ @loaded_fixtures = {}
973
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
974
+ unless fixtures.nil?
975
+ if fixtures.instance_of?(Fixtures)
976
+ @loaded_fixtures[fixtures.name] = fixtures
977
+ else
978
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
979
+ end
980
+ end
981
+ end
982
+
983
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
984
+ @@required_fixture_classes = false
985
+
986
+ def instantiate_fixtures
987
+ if pre_loaded_fixtures
988
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
989
+ unless @@required_fixture_classes
990
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
991
+ @@required_fixture_classes = true
992
+ end
993
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
994
+ else
995
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
996
+ @loaded_fixtures.each do |table_name, fixtures|
997
+ Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ def load_instances?
1003
+ use_instantiated_fixtures != :no_instances
1004
+ end
1005
+ end
1006
+ end