activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+ require "active_record/explain_registry"
5
+
6
+ module ActiveRecord
7
+ class ExplainSubscriber # :nodoc:
8
+ def start(name, id, payload)
9
+ # unused
10
+ end
11
+
12
+ def finish(name, id, payload)
13
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
14
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
15
+ end
16
+ end
17
+
18
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
19
+ # our own EXPLAINs no matter how loopingly beautiful that would be.
20
+ #
21
+ # On the other hand, we want to monitor the performance of our real database
22
+ # queries, not the performance of the access to the query cache.
23
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
24
+ EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
25
+ def ignore_payload?(payload)
26
+ payload[:exception] ||
27
+ payload[:cached] ||
28
+ IGNORED_PAYLOADS.include?(payload[:name]) ||
29
+ payload[:sql] !~ EXPLAINED_SQLS
30
+ end
31
+
32
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
33
+ end
34
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "yaml"
5
+
6
+ module ActiveRecord
7
+ class FixtureSet
8
+ class File # :nodoc:
9
+ include Enumerable
10
+
11
+ ##
12
+ # Open a fixture file named +file+. When called with a block, the block
13
+ # is called with the filehandle and the filehandle is automatically closed
14
+ # when the block finishes.
15
+ def self.open(file)
16
+ x = new file
17
+ block_given? ? yield(x) : x
18
+ end
19
+
20
+ def initialize(file)
21
+ @file = file
22
+ end
23
+
24
+ def each(&block)
25
+ rows.each(&block)
26
+ end
27
+
28
+ def model_class
29
+ config_row["model_class"]
30
+ end
31
+
32
+ private
33
+ def rows
34
+ @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" }
35
+ end
36
+
37
+ def config_row
38
+ @config_row ||= begin
39
+ row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" }
40
+ if row
41
+ row.last
42
+ else
43
+ { 'model_class': nil }
44
+ end
45
+ end
46
+ end
47
+
48
+ def raw_rows
49
+ @raw_rows ||= begin
50
+ data = YAML.load(render(IO.read(@file)))
51
+ data ? validate(data).to_a : []
52
+ rescue ArgumentError, Psych::SyntaxError => error
53
+ raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. 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}", error.backtrace
54
+ end
55
+ end
56
+
57
+ def prepare_erb(content)
58
+ erb = ERB.new(content)
59
+ erb.filename = @file
60
+ erb
61
+ end
62
+
63
+ def render(content)
64
+ context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
65
+ prepare_erb(content).result(context.get_binding)
66
+ end
67
+
68
+ # Validate our unmarshalled data.
69
+ def validate(data)
70
+ unless Hash === data || YAML::Omap === data
71
+ raise Fixture::FormatError, "fixture is not a hash: #{@file}"
72
+ end
73
+
74
+ invalid = data.reject { |_, row| Hash === row }
75
+ if invalid.any?
76
+ raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
77
+ end
78
+ data
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,1065 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "yaml"
5
+ require "zlib"
6
+ require "set"
7
+ require "active_support/dependencies"
8
+ require "active_support/core_ext/digest/uuid"
9
+ require "active_record/fixture_set/file"
10
+ require "active_record/errors"
11
+
12
+ module ActiveRecord
13
+ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
14
+ end
15
+
16
+ # \Fixtures are a way of organizing data that you want to test against; in short, sample data.
17
+ #
18
+ # They are stored in YAML files, one file per model, which are placed in the directory
19
+ # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
20
+ # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
21
+ # The fixture file ends with the +.yml+ file extension, for example:
22
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
23
+ #
24
+ # The format of a fixture file looks like this:
25
+ #
26
+ # rubyonrails:
27
+ # id: 1
28
+ # name: Ruby on Rails
29
+ # url: http://www.rubyonrails.org
30
+ #
31
+ # google:
32
+ # id: 2
33
+ # name: Google
34
+ # url: http://www.google.com
35
+ #
36
+ # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
37
+ # is followed by an indented list of key/value pairs in the "key: value" format. Records are
38
+ # separated by a blank line for your viewing pleasure.
39
+ #
40
+ # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
41
+ # See http://yaml.org/type/omap.html
42
+ # for the specification. You will need ordered fixtures when you have foreign key constraints
43
+ # on keys in the same table. This is commonly needed for tree structures. Example:
44
+ #
45
+ # --- !omap
46
+ # - parent:
47
+ # id: 1
48
+ # parent_id: NULL
49
+ # title: Parent
50
+ # - child:
51
+ # id: 2
52
+ # parent_id: 1
53
+ # title: Child
54
+ #
55
+ # = Using Fixtures in Test Cases
56
+ #
57
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There
58
+ # are two ways to use the fixtures, but first let's take a look at a sample unit test:
59
+ #
60
+ # require 'test_helper'
61
+ #
62
+ # class WebSiteTest < ActiveSupport::TestCase
63
+ # test "web_site_count" do
64
+ # assert_equal 2, WebSite.count
65
+ # end
66
+ # end
67
+ #
68
+ # By default, +test_helper.rb+ will load all of your fixtures into your test
69
+ # database, so this test will succeed.
70
+ #
71
+ # The testing environment will automatically load all the fixtures into the database before each
72
+ # test. To ensure consistent data, the environment deletes the fixtures before running the load.
73
+ #
74
+ # In addition to being available in the database, the fixture's data may also be accessed by
75
+ # using a special dynamic method, which has the same name as the model.
76
+ #
77
+ # Passing in a fixture name to this dynamic method returns the fixture matching this name:
78
+ #
79
+ # test "find one" do
80
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
81
+ # end
82
+ #
83
+ # Passing in multiple fixture names returns all fixtures matching these names:
84
+ #
85
+ # test "find all by name" do
86
+ # assert_equal 2, web_sites(:rubyonrails, :google).length
87
+ # end
88
+ #
89
+ # Passing in no arguments returns all fixtures:
90
+ #
91
+ # test "find all" do
92
+ # assert_equal 2, web_sites.length
93
+ # end
94
+ #
95
+ # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
96
+ #
97
+ # test "find by name that does not exist" do
98
+ # assert_raise(StandardError) { web_sites(:reddit) }
99
+ # end
100
+ #
101
+ # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
102
+ # following tests:
103
+ #
104
+ # test "find_alt_method_1" do
105
+ # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
106
+ # end
107
+ #
108
+ # test "find_alt_method_2" do
109
+ # assert_equal "Ruby on Rails", @rubyonrails.name
110
+ # end
111
+ #
112
+ # In order to use these methods to access fixtured data within your test cases, you must specify one of the
113
+ # following in your ActiveSupport::TestCase-derived class:
114
+ #
115
+ # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
116
+ # self.use_instantiated_fixtures = true
117
+ #
118
+ # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
119
+ # self.use_instantiated_fixtures = :no_instances
120
+ #
121
+ # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
122
+ # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
123
+ # large sets of fixtured data.
124
+ #
125
+ # = Dynamic fixtures with ERB
126
+ #
127
+ # Sometimes you don't care about the content of the fixtures as much as you care about the volume.
128
+ # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
129
+ # testing, like:
130
+ #
131
+ # <% 1.upto(1000) do |i| %>
132
+ # fix_<%= i %>:
133
+ # id: <%= i %>
134
+ # name: guy_<%= i %>
135
+ # <% end %>
136
+ #
137
+ # This will create 1000 very simple fixtures.
138
+ #
139
+ # Using ERB, you can also inject dynamic values into your fixtures with inserts like
140
+ # <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
141
+ # This is however a feature to be used with some caution. The point of fixtures are that they're
142
+ # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
143
+ # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
144
+ # in fixtures are to be considered a code smell.
145
+ #
146
+ # Helper methods defined in a fixture will not be available in other fixtures, to prevent against
147
+ # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
148
+ # that is included in ActiveRecord::FixtureSet.context_class.
149
+ #
150
+ # - define a helper method in <tt>test_helper.rb</tt>
151
+ # module FixtureFileHelpers
152
+ # def file_sha(path)
153
+ # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
154
+ # end
155
+ # end
156
+ # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
157
+ #
158
+ # - use the helper method in a fixture
159
+ # photo:
160
+ # name: kitten.png
161
+ # sha: <%= file_sha 'files/kitten.png' %>
162
+ #
163
+ # = Transactional Tests
164
+ #
165
+ # Test cases can use begin+rollback to isolate their changes to the database instead of having to
166
+ # delete+insert for every test case.
167
+ #
168
+ # class FooTest < ActiveSupport::TestCase
169
+ # self.use_transactional_tests = true
170
+ #
171
+ # test "godzilla" do
172
+ # assert_not_empty Foo.all
173
+ # Foo.destroy_all
174
+ # assert_empty Foo.all
175
+ # end
176
+ #
177
+ # test "godzilla aftermath" do
178
+ # assert_not_empty Foo.all
179
+ # end
180
+ # end
181
+ #
182
+ # If you preload your test database with all fixture data (probably in the rake task) and use
183
+ # transactional tests, then you may omit all fixtures declarations in your test cases since
184
+ # all the data's already there and every case rolls back its changes.
185
+ #
186
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
187
+ # true. This will provide access to fixture data for every table that has been loaded through
188
+ # fixtures (depending on the value of +use_instantiated_fixtures+).
189
+ #
190
+ # When *not* to use transactional tests:
191
+ #
192
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
193
+ # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
194
+ # and rolled back in teardown. Thus, you won't be able to verify
195
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
196
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
197
+ # Use InnoDB, MaxDB, or NDB instead.
198
+ #
199
+ # = Advanced Fixtures
200
+ #
201
+ # Fixtures that don't specify an ID get some extra features:
202
+ #
203
+ # * Stable, autogenerated IDs
204
+ # * Label references for associations (belongs_to, has_one, has_many)
205
+ # * HABTM associations as inline lists
206
+ #
207
+ # There are some more advanced features available even if the id is specified:
208
+ #
209
+ # * Autofilled timestamp columns
210
+ # * Fixture label interpolation
211
+ # * Support for YAML defaults
212
+ #
213
+ # == Stable, Autogenerated IDs
214
+ #
215
+ # Here, have a monkey fixture:
216
+ #
217
+ # george:
218
+ # id: 1
219
+ # name: George the Monkey
220
+ #
221
+ # reginald:
222
+ # id: 2
223
+ # name: Reginald the Pirate
224
+ #
225
+ # Each of these fixtures has two unique identifiers: one for the database
226
+ # and one for the humans. Why don't we generate the primary key instead?
227
+ # Hashing each fixture's label yields a consistent ID:
228
+ #
229
+ # george: # generated id: 503576764
230
+ # name: George the Monkey
231
+ #
232
+ # reginald: # generated id: 324201669
233
+ # name: Reginald the Pirate
234
+ #
235
+ # Active Record looks at the fixture's model class, discovers the correct
236
+ # primary key, and generates it right before inserting the fixture
237
+ # into the database.
238
+ #
239
+ # The generated ID for a given label is constant, so we can discover
240
+ # any fixture's ID without loading anything, as long as we know the label.
241
+ #
242
+ # == Label references for associations (belongs_to, has_one, has_many)
243
+ #
244
+ # Specifying foreign keys in fixtures can be very fragile, not to
245
+ # mention difficult to read. Since Active Record can figure out the ID of
246
+ # any fixture from its label, you can specify FK's by label instead of ID.
247
+ #
248
+ # === belongs_to
249
+ #
250
+ # Let's break out some more monkeys and pirates.
251
+ #
252
+ # ### in pirates.yml
253
+ #
254
+ # reginald:
255
+ # id: 1
256
+ # name: Reginald the Pirate
257
+ # monkey_id: 1
258
+ #
259
+ # ### in monkeys.yml
260
+ #
261
+ # george:
262
+ # id: 1
263
+ # name: George the Monkey
264
+ # pirate_id: 1
265
+ #
266
+ # Add a few more monkeys and pirates and break this into multiple files,
267
+ # and it gets pretty hard to keep track of what's going on. Let's
268
+ # use labels instead of IDs:
269
+ #
270
+ # ### in pirates.yml
271
+ #
272
+ # reginald:
273
+ # name: Reginald the Pirate
274
+ # monkey: george
275
+ #
276
+ # ### in monkeys.yml
277
+ #
278
+ # george:
279
+ # name: George the Monkey
280
+ # pirate: reginald
281
+ #
282
+ # Pow! All is made clear. Active Record reflects on the fixture's model class,
283
+ # finds all the +belongs_to+ associations, and allows you to specify
284
+ # a target *label* for the *association* (monkey: george) rather than
285
+ # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
286
+ #
287
+ # ==== Polymorphic belongs_to
288
+ #
289
+ # Supporting polymorphic relationships is a little bit more complicated, since
290
+ # Active Record needs to know what type your association is pointing at. Something
291
+ # like this should look familiar:
292
+ #
293
+ # ### in fruit.rb
294
+ #
295
+ # belongs_to :eater, polymorphic: true
296
+ #
297
+ # ### in fruits.yml
298
+ #
299
+ # apple:
300
+ # id: 1
301
+ # name: apple
302
+ # eater_id: 1
303
+ # eater_type: Monkey
304
+ #
305
+ # Can we do better? You bet!
306
+ #
307
+ # apple:
308
+ # eater: george (Monkey)
309
+ #
310
+ # Just provide the polymorphic target type and Active Record will take care of the rest.
311
+ #
312
+ # === has_and_belongs_to_many
313
+ #
314
+ # Time to give our monkey some fruit.
315
+ #
316
+ # ### in monkeys.yml
317
+ #
318
+ # george:
319
+ # id: 1
320
+ # name: George the Monkey
321
+ #
322
+ # ### in fruits.yml
323
+ #
324
+ # apple:
325
+ # id: 1
326
+ # name: apple
327
+ #
328
+ # orange:
329
+ # id: 2
330
+ # name: orange
331
+ #
332
+ # grape:
333
+ # id: 3
334
+ # name: grape
335
+ #
336
+ # ### in fruits_monkeys.yml
337
+ #
338
+ # apple_george:
339
+ # fruit_id: 1
340
+ # monkey_id: 1
341
+ #
342
+ # orange_george:
343
+ # fruit_id: 2
344
+ # monkey_id: 1
345
+ #
346
+ # grape_george:
347
+ # fruit_id: 3
348
+ # monkey_id: 1
349
+ #
350
+ # Let's make the HABTM fixture go away.
351
+ #
352
+ # ### in monkeys.yml
353
+ #
354
+ # george:
355
+ # id: 1
356
+ # name: George the Monkey
357
+ # fruits: apple, orange, grape
358
+ #
359
+ # ### in fruits.yml
360
+ #
361
+ # apple:
362
+ # name: apple
363
+ #
364
+ # orange:
365
+ # name: orange
366
+ #
367
+ # grape:
368
+ # name: grape
369
+ #
370
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
371
+ # on George's fixture, but we could've just as easily specified a list
372
+ # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
373
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
374
+ # associations.
375
+ #
376
+ # == Autofilled Timestamp Columns
377
+ #
378
+ # If your table/model specifies any of Active Record's
379
+ # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
380
+ # they will automatically be set to <tt>Time.now</tt>.
381
+ #
382
+ # If you've set specific values, they'll be left alone.
383
+ #
384
+ # == Fixture label interpolation
385
+ #
386
+ # The label of the current fixture is always available as a column value:
387
+ #
388
+ # geeksomnia:
389
+ # name: Geeksomnia's Account
390
+ # subdomain: $LABEL
391
+ # email: $LABEL@email.com
392
+ #
393
+ # Also, sometimes (like when porting older join table fixtures) you'll need
394
+ # to be able to get a hold of the identifier for a given label. ERB
395
+ # to the rescue:
396
+ #
397
+ # george_reginald:
398
+ # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
399
+ # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
400
+ #
401
+ # == Support for YAML defaults
402
+ #
403
+ # You can set and reuse defaults in your fixtures YAML file.
404
+ # This is the same technique used in the +database.yml+ file to specify
405
+ # defaults:
406
+ #
407
+ # DEFAULTS: &DEFAULTS
408
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
409
+ #
410
+ # first:
411
+ # name: Smurf
412
+ # <<: *DEFAULTS
413
+ #
414
+ # second:
415
+ # name: Fraggle
416
+ # <<: *DEFAULTS
417
+ #
418
+ # Any fixture labeled "DEFAULTS" is safely ignored.
419
+ #
420
+ # == Configure the fixture model class
421
+ #
422
+ # It's possible to set the fixture's model class directly in the YAML file.
423
+ # This is helpful when fixtures are loaded outside tests and
424
+ # +set_fixture_class+ is not available (e.g.
425
+ # when running <tt>rails db:fixtures:load</tt>).
426
+ #
427
+ # _fixture:
428
+ # model_class: User
429
+ # david:
430
+ # name: David
431
+ #
432
+ # Any fixtures labeled "_fixture" are safely ignored.
433
+ class FixtureSet
434
+ #--
435
+ # An instance of FixtureSet is normally stored in a single YAML file and
436
+ # possibly in a folder with the same name.
437
+ #++
438
+
439
+ MAX_ID = 2**30 - 1
440
+
441
+ @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
442
+
443
+ def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
444
+ config.pluralize_table_names ?
445
+ fixture_set_name.singularize.camelize :
446
+ fixture_set_name.camelize
447
+ end
448
+
449
+ def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
450
+ "#{ config.table_name_prefix }"\
451
+ "#{ fixture_set_name.tr('/', '_') }"\
452
+ "#{ config.table_name_suffix }".to_sym
453
+ end
454
+
455
+ def self.reset_cache
456
+ @@all_cached_fixtures.clear
457
+ end
458
+
459
+ def self.cache_for_connection(connection)
460
+ @@all_cached_fixtures[connection]
461
+ end
462
+
463
+ def self.fixture_is_cached?(connection, table_name)
464
+ cache_for_connection(connection)[table_name]
465
+ end
466
+
467
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
468
+ if keys_to_fetch
469
+ cache_for_connection(connection).values_at(*keys_to_fetch)
470
+ else
471
+ cache_for_connection(connection).values
472
+ end
473
+ end
474
+
475
+ def self.cache_fixtures(connection, fixtures_map)
476
+ cache_for_connection(connection).update(fixtures_map)
477
+ end
478
+
479
+ def self.instantiate_fixtures(object, fixture_set, load_instances = true)
480
+ if load_instances
481
+ fixture_set.each do |fixture_name, fixture|
482
+ begin
483
+ object.instance_variable_set "@#{fixture_name}", fixture.find
484
+ rescue FixtureClassNotFound
485
+ nil
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
492
+ all_loaded_fixtures.each_value do |fixture_set|
493
+ instantiate_fixtures(object, fixture_set, load_instances)
494
+ end
495
+ end
496
+
497
+ cattr_accessor :all_loaded_fixtures, default: {}
498
+
499
+ class ClassCache
500
+ def initialize(class_names, config)
501
+ @class_names = class_names.stringify_keys
502
+ @config = config
503
+
504
+ # Remove string values that aren't constants or subclasses of AR
505
+ @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
506
+ end
507
+
508
+ def [](fs_name)
509
+ @class_names.fetch(fs_name) {
510
+ klass = default_fixture_model(fs_name, @config).safe_constantize
511
+ insert_class(@class_names, fs_name, klass)
512
+ }
513
+ end
514
+
515
+ private
516
+
517
+ def insert_class(class_names, name, klass)
518
+ # We only want to deal with AR objects.
519
+ if klass && klass < ActiveRecord::Base
520
+ class_names[name] = klass
521
+ else
522
+ class_names[name] = nil
523
+ end
524
+ end
525
+
526
+ def default_fixture_model(fs_name, config)
527
+ ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
528
+ end
529
+ end
530
+
531
+ def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
532
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
533
+ class_names = ClassCache.new class_names, config
534
+
535
+ # FIXME: Apparently JK uses this.
536
+ connection = block_given? ? yield : ActiveRecord::Base.connection
537
+
538
+ files_to_read = fixture_set_names.reject { |fs_name|
539
+ fixture_is_cached?(connection, fs_name)
540
+ }
541
+
542
+ unless files_to_read.empty?
543
+ fixtures_map = {}
544
+
545
+ fixture_sets = files_to_read.map do |fs_name|
546
+ klass = class_names[fs_name]
547
+ conn = klass ? klass.connection : connection
548
+ fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
549
+ conn,
550
+ fs_name,
551
+ klass,
552
+ ::File.join(fixtures_directory, fs_name))
553
+ end
554
+
555
+ update_all_loaded_fixtures fixtures_map
556
+ fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection }
557
+
558
+ fixture_sets_by_connection.each do |conn, set|
559
+ table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
560
+
561
+ set.each do |fs|
562
+ fs.table_rows.each do |table, rows|
563
+ table_rows_for_connection[table].unshift(*rows)
564
+ end
565
+ end
566
+ conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
567
+
568
+ # Cap primary key sequences to max(pk).
569
+ if conn.respond_to?(:reset_pk_sequence!)
570
+ set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
571
+ end
572
+ end
573
+
574
+ cache_fixtures(connection, fixtures_map)
575
+ end
576
+ cached_fixtures(connection, fixture_set_names)
577
+ end
578
+
579
+ # Returns a consistent, platform-independent identifier for +label+.
580
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
581
+ def self.identify(label, column_type = :integer)
582
+ if column_type == :uuid
583
+ Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
584
+ else
585
+ Zlib.crc32(label.to_s) % MAX_ID
586
+ end
587
+ end
588
+
589
+ # Superclass for the evaluation contexts used by ERB fixtures.
590
+ def self.context_class
591
+ @context_class ||= Class.new
592
+ end
593
+
594
+ def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
595
+ all_loaded_fixtures.update(fixtures_map)
596
+ end
597
+
598
+ attr_reader :table_name, :name, :fixtures, :model_class, :config
599
+
600
+ def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
601
+ @name = name
602
+ @path = path
603
+ @config = config
604
+
605
+ self.model_class = class_name
606
+
607
+ @fixtures = read_fixture_files(path)
608
+
609
+ @connection = connection
610
+
611
+ @table_name = (model_class.respond_to?(:table_name) ?
612
+ model_class.table_name :
613
+ self.class.default_fixture_table_name(name, config))
614
+ end
615
+
616
+ def [](x)
617
+ fixtures[x]
618
+ end
619
+
620
+ def []=(k, v)
621
+ fixtures[k] = v
622
+ end
623
+
624
+ def each(&block)
625
+ fixtures.each(&block)
626
+ end
627
+
628
+ def size
629
+ fixtures.size
630
+ end
631
+
632
+ # Returns a hash of rows to be inserted. The key is the table, the value is
633
+ # a list of rows to insert to that table.
634
+ def table_rows
635
+ now = config.default_timezone == :utc ? Time.now.utc : Time.now
636
+
637
+ # allow a standard key to be used for doing defaults in YAML
638
+ fixtures.delete("DEFAULTS")
639
+
640
+ # track any join tables we need to insert later
641
+ rows = Hash.new { |h, table| h[table] = [] }
642
+
643
+ rows[table_name] = fixtures.map do |label, fixture|
644
+ row = fixture.to_hash
645
+
646
+ if model_class
647
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
648
+ if model_class.record_timestamps
649
+ timestamp_column_names.each do |c_name|
650
+ row[c_name] = now unless row.key?(c_name)
651
+ end
652
+ end
653
+
654
+ # interpolate the fixture label
655
+ row.each do |key, value|
656
+ row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
657
+ end
658
+
659
+ # generate a primary key if necessary
660
+ if has_primary_key_column? && !row.include?(primary_key_name)
661
+ row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
662
+ end
663
+
664
+ # Resolve enums
665
+ model_class.defined_enums.each do |name, values|
666
+ if row.include?(name)
667
+ row[name] = values.fetch(row[name], row[name])
668
+ end
669
+ end
670
+
671
+ # If STI is used, find the correct subclass for association reflection
672
+ reflection_class =
673
+ if row.include?(inheritance_column_name)
674
+ row[inheritance_column_name].constantize rescue model_class
675
+ else
676
+ model_class
677
+ end
678
+
679
+ reflection_class._reflections.each_value do |association|
680
+ case association.macro
681
+ when :belongs_to
682
+ # Do not replace association name with association foreign key if they are named the same
683
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
684
+
685
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
686
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
687
+ # support polymorphic belongs_to as "label (Type)"
688
+ row[association.foreign_type] = $1
689
+ end
690
+
691
+ fk_type = reflection_class.type_for_attribute(fk_name).type
692
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
693
+ end
694
+ when :has_many
695
+ if association.options[:through]
696
+ add_join_records(rows, row, HasManyThroughProxy.new(association))
697
+ end
698
+ end
699
+ end
700
+ end
701
+
702
+ row
703
+ end
704
+ rows
705
+ end
706
+
707
+ class ReflectionProxy # :nodoc:
708
+ def initialize(association)
709
+ @association = association
710
+ end
711
+
712
+ def join_table
713
+ @association.join_table
714
+ end
715
+
716
+ def name
717
+ @association.name
718
+ end
719
+
720
+ def primary_key_type
721
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
722
+ end
723
+ end
724
+
725
+ class HasManyThroughProxy < ReflectionProxy # :nodoc:
726
+ def rhs_key
727
+ @association.foreign_key
728
+ end
729
+
730
+ def lhs_key
731
+ @association.through_reflection.foreign_key
732
+ end
733
+
734
+ def join_table
735
+ @association.through_reflection.table_name
736
+ end
737
+ end
738
+
739
+ private
740
+ def primary_key_name
741
+ @primary_key_name ||= model_class && model_class.primary_key
742
+ end
743
+
744
+ def primary_key_type
745
+ @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
746
+ end
747
+
748
+ def add_join_records(rows, row, association)
749
+ # This is the case when the join table has no fixtures file
750
+ if (targets = row.delete(association.name.to_s))
751
+ table_name = association.join_table
752
+ column_type = association.primary_key_type
753
+ lhs_key = association.lhs_key
754
+ rhs_key = association.rhs_key
755
+
756
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
757
+ rows[table_name].concat targets.map { |target|
758
+ { lhs_key => row[primary_key_name],
759
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
760
+ }
761
+ end
762
+ end
763
+
764
+ def has_primary_key_column?
765
+ @has_primary_key_column ||= primary_key_name &&
766
+ model_class.columns.any? { |c| c.name == primary_key_name }
767
+ end
768
+
769
+ def timestamp_column_names
770
+ @timestamp_column_names ||=
771
+ %w(created_at created_on updated_at updated_on) & column_names
772
+ end
773
+
774
+ def inheritance_column_name
775
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
776
+ end
777
+
778
+ def column_names
779
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
780
+ end
781
+
782
+ def model_class=(class_name)
783
+ if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
784
+ @model_class = class_name
785
+ else
786
+ @model_class = class_name.safe_constantize if class_name
787
+ end
788
+ end
789
+
790
+ # Loads the fixtures from the YAML file at +path+.
791
+ # If the file sets the +model_class+ and current instance value is not set,
792
+ # it uses the file value.
793
+ def read_fixture_files(path)
794
+ yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
795
+ ::File.file?(f)
796
+ } + [yaml_file_path(path)]
797
+
798
+ yaml_files.each_with_object({}) do |file, fixtures|
799
+ FixtureSet::File.open(file) do |fh|
800
+ self.model_class ||= fh.model_class if fh.model_class
801
+ fh.each do |fixture_name, row|
802
+ fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
803
+ end
804
+ end
805
+ end
806
+ end
807
+
808
+ def yaml_file_path(path)
809
+ "#{path}.yml"
810
+ end
811
+ end
812
+
813
+ class Fixture #:nodoc:
814
+ include Enumerable
815
+
816
+ class FixtureError < StandardError #:nodoc:
817
+ end
818
+
819
+ class FormatError < FixtureError #:nodoc:
820
+ end
821
+
822
+ attr_reader :model_class, :fixture
823
+
824
+ def initialize(fixture, model_class)
825
+ @fixture = fixture
826
+ @model_class = model_class
827
+ end
828
+
829
+ def class_name
830
+ model_class.name if model_class
831
+ end
832
+
833
+ def each
834
+ fixture.each { |item| yield item }
835
+ end
836
+
837
+ def [](key)
838
+ fixture[key]
839
+ end
840
+
841
+ alias :to_hash :fixture
842
+
843
+ def find
844
+ if model_class
845
+ model_class.unscoped do
846
+ model_class.find(fixture[model_class.primary_key])
847
+ end
848
+ else
849
+ raise FixtureClassNotFound, "No class attached to find."
850
+ end
851
+ end
852
+ end
853
+ end
854
+
855
+ module ActiveRecord
856
+ module TestFixtures
857
+ extend ActiveSupport::Concern
858
+
859
+ def before_setup # :nodoc:
860
+ setup_fixtures
861
+ super
862
+ end
863
+
864
+ def after_teardown # :nodoc:
865
+ super
866
+ teardown_fixtures
867
+ end
868
+
869
+ included do
870
+ class_attribute :fixture_path, instance_writer: false
871
+ class_attribute :fixture_table_names, default: []
872
+ class_attribute :fixture_class_names, default: {}
873
+ class_attribute :use_transactional_tests, default: true
874
+ class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
875
+ class_attribute :pre_loaded_fixtures, default: false
876
+ class_attribute :config, default: ActiveRecord::Base
877
+ end
878
+
879
+ module ClassMethods
880
+ # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
881
+ #
882
+ # Examples:
883
+ #
884
+ # set_fixture_class some_fixture: SomeModel,
885
+ # 'namespaced/fixture' => Another::Model
886
+ #
887
+ # The keys must be the fixture names, that coincide with the short paths to the fixture files.
888
+ def set_fixture_class(class_names = {})
889
+ self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
890
+ end
891
+
892
+ def fixtures(*fixture_set_names)
893
+ if fixture_set_names.first == :all
894
+ fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
895
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
896
+ else
897
+ fixture_set_names = fixture_set_names.flatten.map(&:to_s)
898
+ end
899
+
900
+ self.fixture_table_names |= fixture_set_names
901
+ setup_fixture_accessors(fixture_set_names)
902
+ end
903
+
904
+ def setup_fixture_accessors(fixture_set_names = nil)
905
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
906
+ methods = Module.new do
907
+ fixture_set_names.each do |fs_name|
908
+ fs_name = fs_name.to_s
909
+ accessor_name = fs_name.tr("/", "_").to_sym
910
+
911
+ define_method(accessor_name) do |*fixture_names|
912
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
913
+ return_single_record = fixture_names.size == 1
914
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
915
+
916
+ @fixture_cache[fs_name] ||= {}
917
+
918
+ instances = fixture_names.map do |f_name|
919
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
920
+ @fixture_cache[fs_name].delete(f_name) if force_reload
921
+
922
+ if @loaded_fixtures[fs_name][f_name]
923
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
924
+ else
925
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
926
+ end
927
+ end
928
+
929
+ return_single_record ? instances.first : instances
930
+ end
931
+ private accessor_name
932
+ end
933
+ end
934
+ include methods
935
+ end
936
+
937
+ def uses_transaction(*methods)
938
+ @uses_transaction = [] unless defined?(@uses_transaction)
939
+ @uses_transaction.concat methods.map(&:to_s)
940
+ end
941
+
942
+ def uses_transaction?(method)
943
+ @uses_transaction = [] unless defined?(@uses_transaction)
944
+ @uses_transaction.include?(method.to_s)
945
+ end
946
+ end
947
+
948
+ def run_in_transaction?
949
+ use_transactional_tests &&
950
+ !self.class.uses_transaction?(method_name)
951
+ end
952
+
953
+ def setup_fixtures(config = ActiveRecord::Base)
954
+ if pre_loaded_fixtures && !use_transactional_tests
955
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
956
+ end
957
+
958
+ @fixture_cache = {}
959
+ @fixture_connections = []
960
+ @@already_loaded_fixtures ||= {}
961
+ @connection_subscriber = nil
962
+
963
+ # Load fixtures once and begin transaction.
964
+ if run_in_transaction?
965
+ if @@already_loaded_fixtures[self.class]
966
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
967
+ else
968
+ @loaded_fixtures = load_fixtures(config)
969
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
970
+ end
971
+
972
+ # Begin transactions for connections already established
973
+ @fixture_connections = enlist_fixture_connections
974
+ @fixture_connections.each do |connection|
975
+ connection.begin_transaction joinable: false
976
+ connection.pool.lock_thread = true
977
+ end
978
+
979
+ # When connections are established in the future, begin a transaction too
980
+ @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
981
+ spec_name = payload[:spec_name] if payload.key?(:spec_name)
982
+
983
+ if spec_name
984
+ begin
985
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
986
+ rescue ConnectionNotEstablished
987
+ connection = nil
988
+ end
989
+
990
+ if connection && !@fixture_connections.include?(connection)
991
+ connection.begin_transaction joinable: false
992
+ connection.pool.lock_thread = true
993
+ @fixture_connections << connection
994
+ end
995
+ end
996
+ end
997
+
998
+ # Load fixtures for every test.
999
+ else
1000
+ ActiveRecord::FixtureSet.reset_cache
1001
+ @@already_loaded_fixtures[self.class] = nil
1002
+ @loaded_fixtures = load_fixtures(config)
1003
+ end
1004
+
1005
+ # Instantiate fixtures for every test if requested.
1006
+ instantiate_fixtures if use_instantiated_fixtures
1007
+ end
1008
+
1009
+ def teardown_fixtures
1010
+ # Rollback changes if a transaction is active.
1011
+ if run_in_transaction?
1012
+ ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
1013
+ @fixture_connections.each do |connection|
1014
+ connection.rollback_transaction if connection.transaction_open?
1015
+ connection.pool.lock_thread = false
1016
+ end
1017
+ @fixture_connections.clear
1018
+ else
1019
+ ActiveRecord::FixtureSet.reset_cache
1020
+ end
1021
+
1022
+ ActiveRecord::Base.clear_active_connections!
1023
+ end
1024
+
1025
+ def enlist_fixture_connections
1026
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
1027
+ end
1028
+
1029
+ private
1030
+ def load_fixtures(config)
1031
+ fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
1032
+ Hash[fixtures.map { |f| [f.name, f] }]
1033
+ end
1034
+
1035
+ def instantiate_fixtures
1036
+ if pre_loaded_fixtures
1037
+ raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
1038
+ ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
1039
+ else
1040
+ raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
1041
+ @loaded_fixtures.each_value do |fixture_set|
1042
+ ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
1043
+ end
1044
+ end
1045
+ end
1046
+
1047
+ def load_instances?
1048
+ use_instantiated_fixtures != :no_instances
1049
+ end
1050
+ end
1051
+ end
1052
+
1053
+ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
1054
+ def self.create_subclass
1055
+ Class.new ActiveRecord::FixtureSet.context_class do
1056
+ def get_binding
1057
+ binding()
1058
+ end
1059
+
1060
+ def binary(path)
1061
+ %(!!binary "#{Base64.strict_encode64(File.read(path))}")
1062
+ end
1063
+ end
1064
+ end
1065
+ end