sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,85 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module ActiveRecord
4
+ module Explain
5
+ def self.extended(base)
6
+ base.class_eval do
7
+ # If a query takes longer than these many seconds we log its query plan
8
+ # automatically. nil disables this feature.
9
+ class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false
10
+ self.auto_explain_threshold_in_seconds = nil
11
+ end
12
+ end
13
+
14
+ # If auto explain is enabled, this method triggers EXPLAIN logging for the
15
+ # queries triggered by the block if it takes more than the threshold as a
16
+ # whole. That is, the threshold is not checked against each individual
17
+ # query, but against the duration of the entire block. This approach is
18
+ # convenient for relations.
19
+ #
20
+ # The available_queries_for_explain thread variable collects the queries
21
+ # to be explained. If the value is nil, it means queries are not being
22
+ # currently collected. A false value indicates collecting is turned
23
+ # off. Otherwise it is an array of queries.
24
+ def logging_query_plan # :nodoc:
25
+ return yield unless logger
26
+
27
+ threshold = auto_explain_threshold_in_seconds
28
+ current = Thread.current
29
+ if threshold && current[:available_queries_for_explain].nil?
30
+ begin
31
+ queries = current[:available_queries_for_explain] = []
32
+ start = Time.now
33
+ result = yield
34
+ logger.warn(exec_explain(queries)) if Time.now - start > threshold
35
+ result
36
+ ensure
37
+ current[:available_queries_for_explain] = nil
38
+ end
39
+ else
40
+ yield
41
+ end
42
+ end
43
+
44
+ # Relation#explain needs to be able to collect the queries regardless of
45
+ # whether auto explain is enabled. This method serves that purpose.
46
+ def collecting_queries_for_explain # :nodoc:
47
+ current = Thread.current
48
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
49
+ return yield, current[:available_queries_for_explain]
50
+ ensure
51
+ # Note that the return value above does not depend on this assigment.
52
+ current[:available_queries_for_explain] = original
53
+ end
54
+
55
+ # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
56
+ # Returns a formatted string ready to be logged.
57
+ def exec_explain(queries) # :nodoc:
58
+ queries && queries.map do |sql, bind|
59
+ [].tap do |msg|
60
+ msg << "EXPLAIN for: #{sql}"
61
+ unless bind.empty?
62
+ bind_msg = bind.map {|col, val| [col.name, val]}.inspect
63
+ msg.last << " #{bind_msg}"
64
+ end
65
+ msg << connection.explain(sql, bind)
66
+ end.join("\n")
67
+ end.join("\n")
68
+ end
69
+
70
+ # Silences automatic EXPLAIN logging for the duration of the block.
71
+ #
72
+ # This has high priority, no EXPLAINs will be run even if downwards
73
+ # the threshold is set to 0.
74
+ #
75
+ # As the name of the method suggests this only applies to automatic
76
+ # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
77
+ def silence_auto_explain
78
+ current = Thread.current
79
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
80
+ yield
81
+ ensure
82
+ current[:available_queries_for_explain] = original
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support/notifications'
2
+
3
+ module ActiveRecord
4
+ class ExplainSubscriber # :nodoc:
5
+ def call(*args)
6
+ if queries = Thread.current[:available_queries_for_explain]
7
+ payload = args.last
8
+ queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
9
+ end
10
+ end
11
+
12
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
13
+ # our own EXPLAINs now matter how loopingly beautiful that would be.
14
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
15
+ def ignore_payload?(payload)
16
+ payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name])
17
+ end
18
+
19
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
20
+ end
21
+ end
@@ -0,0 +1,906 @@
1
+ require 'erb'
2
+
3
+ begin
4
+ require 'psych'
5
+ rescue LoadError
6
+ end
7
+
8
+ require 'yaml'
9
+ require 'zlib'
10
+ require 'active_support/dependencies'
11
+ require 'active_support/core_ext/array/wrap'
12
+ require 'active_support/core_ext/object/blank'
13
+ require 'active_support/core_ext/logger'
14
+ require 'active_support/ordered_hash'
15
+ require 'active_record/fixtures/file'
16
+
17
+ if defined? ActiveRecord
18
+ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
19
+ end
20
+ else
21
+ class FixtureClassNotFound < StandardError #:nodoc:
22
+ end
23
+ end
24
+
25
+ module ActiveRecord
26
+ # \Fixtures are a way of organizing data that you want to test against; in short, sample data.
27
+ #
28
+ # They are stored in YAML files, one file per model, which are placed in the directory
29
+ # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
30
+ # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
31
+ # The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
32
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a fixture file looks
33
+ # like this:
34
+ #
35
+ # rubyonrails:
36
+ # id: 1
37
+ # name: Ruby on Rails
38
+ # url: http://www.rubyonrails.org
39
+ #
40
+ # google:
41
+ # id: 2
42
+ # name: Google
43
+ # url: http://www.google.com
44
+ #
45
+ # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
46
+ # is followed by an indented list of key/value pairs in the "key: value" format. Records are
47
+ # separated by a blank line for your viewing pleasure.
48
+ #
49
+ # Note that fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
50
+ # See http://yaml.org/type/omap.html
51
+ # for the specification. You will need ordered fixtures when you have foreign key constraints
52
+ # on keys in the same table. This is commonly needed for tree structures. Example:
53
+ #
54
+ # --- !omap
55
+ # - parent:
56
+ # id: 1
57
+ # parent_id: NULL
58
+ # title: Parent
59
+ # - child:
60
+ # id: 2
61
+ # parent_id: 1
62
+ # title: Child
63
+ #
64
+ # = Using Fixtures in Test Cases
65
+ #
66
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There
67
+ # are two ways to use the fixtures, but first let's take a look at a sample unit test:
68
+ #
69
+ # require 'test_helper'
70
+ #
71
+ # class WebSiteTest < ActiveSupport::TestCase
72
+ # test "web_site_count" do
73
+ # assert_equal 2, WebSite.count
74
+ # end
75
+ # end
76
+ #
77
+ # By default, <tt>test_helper.rb</tt> will load all of your fixtures into your test database,
78
+ # so this test will succeed.
79
+ #
80
+ # The testing environment will automatically load the all fixtures into the database before each
81
+ # test. To ensure consistent data, the environment deletes the fixtures before running the load.
82
+ #
83
+ # In addition to being available in the database, the fixture's data may also be accessed by
84
+ # using a special dynamic method, which has the same name as the model, and accepts the
85
+ # name of the fixture to instantiate:
86
+ #
87
+ # test "find" do
88
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
89
+ # end
90
+ #
91
+ # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
92
+ # following tests:
93
+ #
94
+ # test "find_alt_method_1" do
95
+ # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
96
+ # end
97
+ #
98
+ # test "find_alt_method_2" do
99
+ # assert_equal "Ruby on Rails", @rubyonrails.news
100
+ # end
101
+ #
102
+ # In order to use these methods to access fixtured data within your testcases, you must specify one of the
103
+ # following in your <tt>ActiveSupport::TestCase</tt>-derived class:
104
+ #
105
+ # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
106
+ # self.use_instantiated_fixtures = true
107
+ #
108
+ # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
109
+ # self.use_instantiated_fixtures = :no_instances
110
+ #
111
+ # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
112
+ # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
113
+ # large sets of fixtured data.
114
+ #
115
+ # = Dynamic fixtures with ERB
116
+ #
117
+ # Some times you don't care about the content of the fixtures as much as you care about the volume.
118
+ # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
119
+ # testing, like:
120
+ #
121
+ # <% 1.upto(1000) do |i| %>
122
+ # fix_<%= i %>:
123
+ # id: <%= i %>
124
+ # name: guy_<%= 1 %>
125
+ # <% end %>
126
+ #
127
+ # This will create 1000 very simple fixtures.
128
+ #
129
+ # Using ERB, you can also inject dynamic values into your fixtures with inserts like
130
+ # <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
131
+ # This is however a feature to be used with some caution. The point of fixtures are that they're
132
+ # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
133
+ # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
134
+ # in fixtures are to be considered a code smell.
135
+ #
136
+ # = Transactional Fixtures
137
+ #
138
+ # Test cases can use begin+rollback to isolate their changes to the database instead of having to
139
+ # delete+insert for every test case.
140
+ #
141
+ # class FooTest < ActiveSupport::TestCase
142
+ # self.use_transactional_fixtures = true
143
+ #
144
+ # test "godzilla" do
145
+ # assert !Foo.all.empty?
146
+ # Foo.destroy_all
147
+ # assert Foo.all.empty?
148
+ # end
149
+ #
150
+ # test "godzilla aftermath" do
151
+ # assert !Foo.all.empty?
152
+ # end
153
+ # end
154
+ #
155
+ # If you preload your test database with all fixture data (probably in the rake task) and use
156
+ # transactional fixtures, then you may omit all fixtures declarations in your test cases since
157
+ # all the data's already there and every case rolls back its changes.
158
+ #
159
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
160
+ # true. This will provide access to fixture data for every table that has been loaded through
161
+ # fixtures (depending on the value of +use_instantiated_fixtures+).
162
+ #
163
+ # When *not* to use transactional fixtures:
164
+ #
165
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
166
+ # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
167
+ # and rolled back in teardown. Thus, you won't be able to verify
168
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
169
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
170
+ # Use InnoDB, MaxDB, or NDB instead.
171
+ #
172
+ # = Advanced Fixtures
173
+ #
174
+ # Fixtures that don't specify an ID get some extra features:
175
+ #
176
+ # * Stable, autogenerated IDs
177
+ # * Label references for associations (belongs_to, has_one, has_many)
178
+ # * HABTM associations as inline lists
179
+ # * Autofilled timestamp columns
180
+ # * Fixture label interpolation
181
+ # * Support for YAML defaults
182
+ #
183
+ # == Stable, Autogenerated IDs
184
+ #
185
+ # Here, have a monkey fixture:
186
+ #
187
+ # george:
188
+ # id: 1
189
+ # name: George the Monkey
190
+ #
191
+ # reginald:
192
+ # id: 2
193
+ # name: Reginald the Pirate
194
+ #
195
+ # Each of these fixtures has two unique identifiers: one for the database
196
+ # and one for the humans. Why don't we generate the primary key instead?
197
+ # Hashing each fixture's label yields a consistent ID:
198
+ #
199
+ # george: # generated id: 503576764
200
+ # name: George the Monkey
201
+ #
202
+ # reginald: # generated id: 324201669
203
+ # name: Reginald the Pirate
204
+ #
205
+ # Active Record looks at the fixture's model class, discovers the correct
206
+ # primary key, and generates it right before inserting the fixture
207
+ # into the database.
208
+ #
209
+ # The generated ID for a given label is constant, so we can discover
210
+ # any fixture's ID without loading anything, as long as we know the label.
211
+ #
212
+ # == Label references for associations (belongs_to, has_one, has_many)
213
+ #
214
+ # Specifying foreign keys in fixtures can be very fragile, not to
215
+ # mention difficult to read. Since Active Record can figure out the ID of
216
+ # any fixture from its label, you can specify FK's by label instead of ID.
217
+ #
218
+ # === belongs_to
219
+ #
220
+ # Let's break out some more monkeys and pirates.
221
+ #
222
+ # ### in pirates.yml
223
+ #
224
+ # reginald:
225
+ # id: 1
226
+ # name: Reginald the Pirate
227
+ # monkey_id: 1
228
+ #
229
+ # ### in monkeys.yml
230
+ #
231
+ # george:
232
+ # id: 1
233
+ # name: George the Monkey
234
+ # pirate_id: 1
235
+ #
236
+ # Add a few more monkeys and pirates and break this into multiple files,
237
+ # and it gets pretty hard to keep track of what's going on. Let's
238
+ # use labels instead of IDs:
239
+ #
240
+ # ### in pirates.yml
241
+ #
242
+ # reginald:
243
+ # name: Reginald the Pirate
244
+ # monkey: george
245
+ #
246
+ # ### in monkeys.yml
247
+ #
248
+ # george:
249
+ # name: George the Monkey
250
+ # pirate: reginald
251
+ #
252
+ # Pow! All is made clear. Active Record reflects on the fixture's model class,
253
+ # finds all the +belongs_to+ associations, and allows you to specify
254
+ # a target *label* for the *association* (monkey: george) rather than
255
+ # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
256
+ #
257
+ # ==== Polymorphic belongs_to
258
+ #
259
+ # Supporting polymorphic relationships is a little bit more complicated, since
260
+ # Active Record needs to know what type your association is pointing at. Something
261
+ # like this should look familiar:
262
+ #
263
+ # ### in fruit.rb
264
+ #
265
+ # belongs_to :eater, :polymorphic => true
266
+ #
267
+ # ### in fruits.yml
268
+ #
269
+ # apple:
270
+ # id: 1
271
+ # name: apple
272
+ # eater_id: 1
273
+ # eater_type: Monkey
274
+ #
275
+ # Can we do better? You bet!
276
+ #
277
+ # apple:
278
+ # eater: george (Monkey)
279
+ #
280
+ # Just provide the polymorphic target type and Active Record will take care of the rest.
281
+ #
282
+ # === has_and_belongs_to_many
283
+ #
284
+ # Time to give our monkey some fruit.
285
+ #
286
+ # ### in monkeys.yml
287
+ #
288
+ # george:
289
+ # id: 1
290
+ # name: George the Monkey
291
+ #
292
+ # ### in fruits.yml
293
+ #
294
+ # apple:
295
+ # id: 1
296
+ # name: apple
297
+ #
298
+ # orange:
299
+ # id: 2
300
+ # name: orange
301
+ #
302
+ # grape:
303
+ # id: 3
304
+ # name: grape
305
+ #
306
+ # ### in fruits_monkeys.yml
307
+ #
308
+ # apple_george:
309
+ # fruit_id: 1
310
+ # monkey_id: 1
311
+ #
312
+ # orange_george:
313
+ # fruit_id: 2
314
+ # monkey_id: 1
315
+ #
316
+ # grape_george:
317
+ # fruit_id: 3
318
+ # monkey_id: 1
319
+ #
320
+ # Let's make the HABTM fixture go away.
321
+ #
322
+ # ### in monkeys.yml
323
+ #
324
+ # george:
325
+ # id: 1
326
+ # name: George the Monkey
327
+ # fruits: apple, orange, grape
328
+ #
329
+ # ### in fruits.yml
330
+ #
331
+ # apple:
332
+ # name: apple
333
+ #
334
+ # orange:
335
+ # name: orange
336
+ #
337
+ # grape:
338
+ # name: grape
339
+ #
340
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
341
+ # on George's fixture, but we could've just as easily specified a list
342
+ # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
343
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
344
+ # associations.
345
+ #
346
+ # == Autofilled Timestamp Columns
347
+ #
348
+ # If your table/model specifies any of Active Record's
349
+ # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
350
+ # they will automatically be set to <tt>Time.now</tt>.
351
+ #
352
+ # If you've set specific values, they'll be left alone.
353
+ #
354
+ # == Fixture label interpolation
355
+ #
356
+ # The label of the current fixture is always available as a column value:
357
+ #
358
+ # geeksomnia:
359
+ # name: Geeksomnia's Account
360
+ # subdomain: $LABEL
361
+ #
362
+ # Also, sometimes (like when porting older join table fixtures) you'll need
363
+ # to be able to get a hold of the identifier for a given label. ERB
364
+ # to the rescue:
365
+ #
366
+ # george_reginald:
367
+ # monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
368
+ # pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
369
+ #
370
+ # == Support for YAML defaults
371
+ #
372
+ # You probably already know how to use YAML to set and reuse defaults in
373
+ # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
374
+ #
375
+ # DEFAULTS: &DEFAULTS
376
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
377
+ #
378
+ # first:
379
+ # name: Smurf
380
+ # *DEFAULTS
381
+ #
382
+ # second:
383
+ # name: Fraggle
384
+ # *DEFAULTS
385
+ #
386
+ # Any fixture labeled "DEFAULTS" is safely ignored.
387
+ class Fixtures
388
+ MAX_ID = 2 ** 30 - 1
389
+
390
+ @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
391
+
392
+ def self.find_table_name(table_name) # :nodoc:
393
+ ActiveRecord::Base.pluralize_table_names ?
394
+ table_name.to_s.singularize.camelize :
395
+ table_name.to_s.camelize
396
+ end
397
+
398
+ def self.reset_cache
399
+ @@all_cached_fixtures.clear
400
+ end
401
+
402
+ def self.cache_for_connection(connection)
403
+ @@all_cached_fixtures[connection]
404
+ end
405
+
406
+ def self.fixture_is_cached?(connection, table_name)
407
+ cache_for_connection(connection)[table_name]
408
+ end
409
+
410
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
411
+ if keys_to_fetch
412
+ cache_for_connection(connection).values_at(*keys_to_fetch)
413
+ else
414
+ cache_for_connection(connection).values
415
+ end
416
+ end
417
+
418
+ def self.cache_fixtures(connection, fixtures_map)
419
+ cache_for_connection(connection).update(fixtures_map)
420
+ end
421
+
422
+ def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
423
+ if load_instances
424
+ fixtures.each do |name, fixture|
425
+ begin
426
+ object.instance_variable_set "@#{name}", fixture.find
427
+ rescue FixtureClassNotFound
428
+ nil
429
+ end
430
+ end
431
+ end
432
+ end
433
+
434
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
435
+ all_loaded_fixtures.each do |table_name, fixtures|
436
+ ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
437
+ end
438
+ end
439
+
440
+ cattr_accessor :all_loaded_fixtures
441
+ self.all_loaded_fixtures = {}
442
+
443
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
444
+ table_names = [table_names].flatten.map { |n| n.to_s }
445
+ table_names.each { |n|
446
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
447
+ }
448
+
449
+ # FIXME: Apparently JK uses this.
450
+ connection = block_given? ? yield : ActiveRecord::Base.connection
451
+
452
+ files_to_read = table_names.reject { |table_name|
453
+ fixture_is_cached?(connection, table_name)
454
+ }
455
+
456
+ unless files_to_read.empty?
457
+ connection.disable_referential_integrity do
458
+ fixtures_map = {}
459
+
460
+ fixture_files = files_to_read.map do |path|
461
+ table_name = path.tr '/', '_'
462
+
463
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
464
+ connection,
465
+ table_name,
466
+ class_names[table_name.to_sym] || table_name.classify,
467
+ ::File.join(fixtures_directory, path))
468
+ end
469
+
470
+ all_loaded_fixtures.update(fixtures_map)
471
+
472
+ connection.transaction(:requires_new => true) do
473
+ fixture_files.each do |ff|
474
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
475
+ table_rows = ff.table_rows
476
+
477
+ table_rows.keys.each do |table|
478
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
479
+ end
480
+
481
+ table_rows.each do |table_name,rows|
482
+ rows.each do |row|
483
+ conn.insert_fixture(row, table_name)
484
+ end
485
+ end
486
+ end
487
+
488
+ # Cap primary key sequences to max(pk).
489
+ if connection.respond_to?(:reset_pk_sequence!)
490
+ table_names.each do |table_name|
491
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
492
+ end
493
+ end
494
+ end
495
+
496
+ cache_fixtures(connection, fixtures_map)
497
+ end
498
+ end
499
+ cached_fixtures(connection, table_names)
500
+ end
501
+
502
+ # Returns a consistent, platform-independent identifier for +label+.
503
+ # Identifiers are positive integers less than 2^32.
504
+ def self.identify(label)
505
+ Zlib.crc32(label.to_s) % MAX_ID
506
+ end
507
+
508
+ attr_reader :table_name, :name, :fixtures, :model_class
509
+
510
+ def initialize(connection, table_name, class_name, fixture_path)
511
+ @connection = connection
512
+ @table_name = table_name
513
+ @fixture_path = fixture_path
514
+ @name = table_name # preserve fixture base name
515
+ @class_name = class_name
516
+
517
+ @fixtures = ActiveSupport::OrderedHash.new
518
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
519
+
520
+ # Should be an AR::Base type class
521
+ if class_name.is_a?(Class)
522
+ @table_name = class_name.table_name
523
+ @connection = class_name.connection
524
+ @model_class = class_name
525
+ else
526
+ @model_class = class_name.constantize rescue nil
527
+ end
528
+
529
+ read_fixture_files
530
+ end
531
+
532
+ def [](x)
533
+ fixtures[x]
534
+ end
535
+
536
+ def []=(k,v)
537
+ fixtures[k] = v
538
+ end
539
+
540
+ def each(&block)
541
+ fixtures.each(&block)
542
+ end
543
+
544
+ def size
545
+ fixtures.size
546
+ end
547
+
548
+ # Return a hash of rows to be inserted. The key is the table, the value is
549
+ # a list of rows to insert to that table.
550
+ def table_rows
551
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
552
+ now = now.to_s(:db)
553
+
554
+ # allow a standard key to be used for doing defaults in YAML
555
+ fixtures.delete('DEFAULTS')
556
+
557
+ # track any join tables we need to insert later
558
+ rows = Hash.new { |h,table| h[table] = [] }
559
+
560
+ rows[table_name] = fixtures.map do |label, fixture|
561
+ row = fixture.to_hash
562
+
563
+ if model_class && model_class < ActiveRecord::Base
564
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
565
+ if model_class.record_timestamps
566
+ timestamp_column_names.each do |name|
567
+ row[name] = now unless row.key?(name)
568
+ end
569
+ end
570
+
571
+ # interpolate the fixture label
572
+ row.each do |key, value|
573
+ row[key] = label if value == "$LABEL"
574
+ end
575
+
576
+ # generate a primary key if necessary
577
+ if has_primary_key_column? && !row.include?(primary_key_name)
578
+ row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
579
+ end
580
+
581
+ # If STI is used, find the correct subclass for association reflection
582
+ reflection_class =
583
+ if row.include?(inheritance_column_name)
584
+ row[inheritance_column_name].constantize rescue model_class
585
+ else
586
+ model_class
587
+ end
588
+
589
+ reflection_class.reflect_on_all_associations.each do |association|
590
+ case association.macro
591
+ when :belongs_to
592
+ # Do not replace association name with association foreign key if they are named the same
593
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
594
+
595
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
596
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
597
+ # support polymorphic belongs_to as "label (Type)"
598
+ row[association.foreign_type] = $1
599
+ end
600
+
601
+ row[fk_name] = ActiveRecord::Fixtures.identify(value)
602
+ end
603
+ when :has_and_belongs_to_many
604
+ if (targets = row.delete(association.name.to_s))
605
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
606
+ table_name = association.options[:join_table]
607
+ rows[table_name].concat targets.map { |target|
608
+ { association.foreign_key => row[primary_key_name],
609
+ association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
610
+ }
611
+ end
612
+ end
613
+ end
614
+ end
615
+
616
+ row
617
+ end
618
+ rows
619
+ end
620
+
621
+ private
622
+ def primary_key_name
623
+ @primary_key_name ||= model_class && model_class.primary_key
624
+ end
625
+
626
+ def has_primary_key_column?
627
+ @has_primary_key_column ||= primary_key_name &&
628
+ model_class.columns.any? { |c| c.name == primary_key_name }
629
+ end
630
+
631
+ def timestamp_column_names
632
+ @timestamp_column_names ||=
633
+ %w(created_at created_on updated_at updated_on) & column_names
634
+ end
635
+
636
+ def inheritance_column_name
637
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
638
+ end
639
+
640
+ def column_names
641
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
642
+ end
643
+
644
+ def read_fixture_files
645
+ yaml_files = Dir["#{@fixture_path}/**/*.yml"].select { |f|
646
+ ::File.file?(f)
647
+ } + [yaml_file_path]
648
+
649
+ yaml_files.each do |file|
650
+ Fixtures::File.open(file) do |fh|
651
+ fh.each do |name, row|
652
+ fixtures[name] = ActiveRecord::Fixture.new(row, model_class)
653
+ end
654
+ end
655
+ end
656
+ end
657
+
658
+ def yaml_file_path
659
+ "#{@fixture_path}.yml"
660
+ end
661
+
662
+ def yaml_fixtures_key(path)
663
+ ::File.basename(@fixture_path).split(".").first
664
+ end
665
+ end
666
+
667
+ class Fixture #:nodoc:
668
+ include Enumerable
669
+
670
+ class FixtureError < StandardError #:nodoc:
671
+ end
672
+
673
+ class FormatError < FixtureError #:nodoc:
674
+ end
675
+
676
+ attr_reader :model_class, :fixture
677
+
678
+ def initialize(fixture, model_class)
679
+ @fixture = fixture
680
+ @model_class = model_class
681
+ end
682
+
683
+ def class_name
684
+ model_class.name if model_class
685
+ end
686
+
687
+ def each
688
+ fixture.each { |item| yield item }
689
+ end
690
+
691
+ def [](key)
692
+ fixture[key]
693
+ end
694
+
695
+ alias :to_hash :fixture
696
+
697
+ def find
698
+ if model_class
699
+ model_class.find(fixture[model_class.primary_key])
700
+ else
701
+ raise FixtureClassNotFound, "No class attached to find."
702
+ end
703
+ end
704
+ end
705
+ end
706
+
707
+ module ActiveRecord
708
+ module TestFixtures
709
+ extend ActiveSupport::Concern
710
+
711
+ included do
712
+ setup :setup_fixtures
713
+ teardown :teardown_fixtures
714
+
715
+ class_attribute :fixture_path
716
+ class_attribute :fixture_table_names
717
+ class_attribute :fixture_class_names
718
+ class_attribute :use_transactional_fixtures
719
+ class_attribute :use_instantiated_fixtures # true, false, or :no_instances
720
+ class_attribute :pre_loaded_fixtures
721
+
722
+ self.fixture_table_names = []
723
+ self.use_transactional_fixtures = true
724
+ self.use_instantiated_fixtures = false
725
+ self.pre_loaded_fixtures = false
726
+
727
+ self.fixture_class_names = Hash.new do |h, table_name|
728
+ h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
729
+ end
730
+ end
731
+
732
+ module ClassMethods
733
+ def set_fixture_class(class_names = {})
734
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
735
+ end
736
+
737
+ def fixtures(*fixture_names)
738
+ if fixture_names.first == :all
739
+ fixture_names = Dir["#{fixture_path}/**/*.{yml}"]
740
+ fixture_names.map! { |f| f[(fixture_path.size + 1)..-5] }
741
+ else
742
+ fixture_names = fixture_names.flatten.map { |n| n.to_s }
743
+ end
744
+
745
+ self.fixture_table_names |= fixture_names
746
+ require_fixture_classes(fixture_names)
747
+ setup_fixture_accessors(fixture_names)
748
+ end
749
+
750
+ def try_to_load_dependency(file_name)
751
+ require_dependency file_name
752
+ rescue LoadError => e
753
+ # Let's hope the developer has included it himself
754
+
755
+ # Let's warn in case this is a subdependency, otherwise
756
+ # subdependency error messages are totally cryptic
757
+ if ActiveRecord::Base.logger
758
+ ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
759
+ end
760
+ end
761
+
762
+ def require_fixture_classes(fixture_names = nil)
763
+ (fixture_names || fixture_table_names).each do |fixture_name|
764
+ file_name = fixture_name.to_s
765
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
766
+ try_to_load_dependency(file_name)
767
+ end
768
+ end
769
+
770
+ def setup_fixture_accessors(fixture_names = nil)
771
+ fixture_names = Array.wrap(fixture_names || fixture_table_names)
772
+ methods = Module.new do
773
+ fixture_names.each do |fixture_name|
774
+ fixture_name = fixture_name.to_s.tr('./', '_')
775
+
776
+ define_method(fixture_name) do |*fixtures|
777
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
778
+
779
+ @fixture_cache[fixture_name] ||= {}
780
+
781
+ instances = fixtures.map do |fixture|
782
+ @fixture_cache[fixture_name].delete(fixture) if force_reload
783
+
784
+ if @loaded_fixtures[fixture_name][fixture.to_s]
785
+ ActiveRecord::IdentityMap.without do
786
+ @fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
787
+ end
788
+ else
789
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{fixture_name}'"
790
+ end
791
+ end
792
+
793
+ instances.size == 1 ? instances.first : instances
794
+ end
795
+ private fixture_name
796
+ end
797
+ end
798
+ include methods
799
+ end
800
+
801
+ def uses_transaction(*methods)
802
+ @uses_transaction = [] unless defined?(@uses_transaction)
803
+ @uses_transaction.concat methods.map { |m| m.to_s }
804
+ end
805
+
806
+ def uses_transaction?(method)
807
+ @uses_transaction = [] unless defined?(@uses_transaction)
808
+ @uses_transaction.include?(method.to_s)
809
+ end
810
+ end
811
+
812
+ def run_in_transaction?
813
+ use_transactional_fixtures &&
814
+ !self.class.uses_transaction?(method_name)
815
+ end
816
+
817
+ def setup_fixtures
818
+ return unless !ActiveRecord::Base.configurations.blank?
819
+
820
+ if pre_loaded_fixtures && !use_transactional_fixtures
821
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
822
+ end
823
+
824
+ @fixture_cache = {}
825
+ @fixture_connections = []
826
+ @@already_loaded_fixtures ||= {}
827
+
828
+ # Load fixtures once and begin transaction.
829
+ if run_in_transaction?
830
+ if @@already_loaded_fixtures[self.class]
831
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
832
+ else
833
+ @loaded_fixtures = load_fixtures
834
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
835
+ end
836
+ @fixture_connections = enlist_fixture_connections
837
+ @fixture_connections.each do |connection|
838
+ connection.increment_open_transactions
839
+ connection.transaction_joinable = false
840
+ connection.begin_db_transaction
841
+ end
842
+ # Load fixtures for every test.
843
+ else
844
+ ActiveRecord::Fixtures.reset_cache
845
+ @@already_loaded_fixtures[self.class] = nil
846
+ @loaded_fixtures = load_fixtures
847
+ end
848
+
849
+ # Instantiate fixtures for every test if requested.
850
+ instantiate_fixtures if use_instantiated_fixtures
851
+ end
852
+
853
+ def teardown_fixtures
854
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
855
+
856
+ unless run_in_transaction?
857
+ ActiveRecord::Fixtures.reset_cache
858
+ end
859
+
860
+ # Rollback changes if a transaction is active.
861
+ if run_in_transaction?
862
+ @fixture_connections.each do |connection|
863
+ if connection.open_transactions != 0
864
+ connection.rollback_db_transaction
865
+ connection.decrement_open_transactions
866
+ end
867
+ end
868
+ @fixture_connections.clear
869
+ end
870
+ ActiveRecord::Base.clear_active_connections!
871
+ end
872
+
873
+ def enlist_fixture_connections
874
+ ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection)
875
+ end
876
+
877
+ private
878
+ def load_fixtures
879
+ fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
880
+ Hash[fixtures.map { |f| [f.name, f] }]
881
+ end
882
+
883
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
884
+ @@required_fixture_classes = false
885
+
886
+ def instantiate_fixtures
887
+ if pre_loaded_fixtures
888
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
889
+ unless @@required_fixture_classes
890
+ self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
891
+ @@required_fixture_classes = true
892
+ end
893
+ ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
894
+ else
895
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
896
+ @loaded_fixtures.each do |fixture_name, fixtures|
897
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
898
+ end
899
+ end
900
+ end
901
+
902
+ def load_instances?
903
+ use_instantiated_fixtures != :no_instances
904
+ end
905
+ end
906
+ end