Skip to content

Commit 5dbde49

Browse files
committed
improved transactions documentation
1 parent 8aad725 commit 5dbde49

File tree

4 files changed

+185
-104
lines changed

4 files changed

+185
-104
lines changed

features/.nav

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
- GettingStarted.md (Start from scratch)
22
- Generators.md (Generators)
3-
- Transactions.md
3+
- ActiveRecord.md
44
- directory_structure.feature
55
- backtrace_filtering.feature
66
- model_specs:

features/ActiveRecord.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Active Record
2+
3+
`rspec-rails` by default injects [ActiveSupport::TestCase](https://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) and exposes some of the settings to RSpec configuration.
4+
Furthermore it adds special hooks into `before` and `after` which are essential for [Active Record Fixtures](https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html) and keeping the database in a clean state.
5+
6+
It provides the `fixtures` class method in the rspec context to tell Rails which fixtures to prepare before each example.
7+
8+
In addition to being available in the database, the fixture’s data may also be accessed by using a special dynamic method, which has the same name as the model.
9+
10+
```ruby
11+
RSpec.configure do |config|
12+
config.fixture_paths = [
13+
Rails.root.join('spec/fixtures')
14+
]
15+
end
16+
17+
RSpec.describe Thing, type: :model do
18+
fixtures :things
19+
20+
it "fixture method defined" do
21+
expect(things(:one)).to eq(Thing.find_by(name: "one"))
22+
end
23+
end
24+
```
25+
26+
More details on how to use fixtures are in the [Rails documentation](https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html#class-ActiveRecord::FixtureSet-label-Using+Fixtures+in+Test+Cases)
27+
28+
## Transactions
29+
30+
When you run `rails generate rspec:install`, the `spec/rails_helper.rb` file
31+
includes the following configuration:
32+
33+
```ruby
34+
RSpec.configure do |config|
35+
config.use_transactional_fixtures = true
36+
end
37+
```
38+
39+
The name of this setting is a bit misleading. What it really means in Rails
40+
is "run every test method within a transaction." In the context of rspec-rails,
41+
it means "run every example within a transaction."
42+
43+
The idea is to start each example with a clean database, create whatever data
44+
is necessary for that example, and then remove that data by simply rolling back
45+
the transaction at the end of the example.
46+
47+
For how this affects methods exposing transaction visibility see:
48+
https://guides.rubyonrails.org/testing.html#transactions
49+
50+
### Data created in `before` are rolled back
51+
52+
Any data you create in a `before` hook will be rolled back at the end of
53+
the example. This is a good thing because it means that each example is
54+
isolated from state that would otherwise be left around by the examples that
55+
already ran. For example:
56+
57+
```ruby
58+
describe Widget do
59+
before do
60+
@widget = Widget.create
61+
end
62+
63+
it "does something" do
64+
expect(@widget).to do_something
65+
end
66+
67+
it "does something else" do
68+
expect(@widget).to do_something_else
69+
end
70+
end
71+
```
72+
73+
The `@widget` is recreated in each of the two examples above, so each example
74+
has a different object, _and_ the underlying data is rolled back so the data
75+
backing the `@widget` in each example is new.
76+
77+
### Data created in `before(:context)` are _not_ rolled back
78+
79+
`before(:context)` hooks are invoked before the transaction is opened. You can use
80+
this to speed things up by creating data once before any example in a group is
81+
run, however, this introduces a number of complications and you should only do
82+
this if you have a firm grasp of the implications. Here are a couple of
83+
guidelines:
84+
85+
1. Be sure to clean up any data in an `after(:context)` hook:
86+
87+
```ruby
88+
before(:context) do
89+
@widget = Widget.create!
90+
end
91+
92+
after(:context) do
93+
@widget.destroy
94+
end
95+
```
96+
97+
If you don't do that, you'll leave data lying around that will eventually
98+
interfere with other examples.
99+
100+
2. Reload the object in a `before` hook.
101+
102+
```ruby
103+
before(:context) do
104+
@widget = Widget.create!
105+
end
106+
107+
before do
108+
@widget.reload
109+
end
110+
```
111+
112+
Even though database updates in each example will be rolled back, the
113+
object won't _know_ about those rollbacks so the object and its backing
114+
data can easily get out of sync.
115+
116+
## Configuration
117+
118+
### Disabling Active Record support
119+
120+
If you prefer to manage the data yourself, or using another tool like
121+
[database_cleaner](https://github.com/bmabey/database_cleaner) to do it for you,
122+
simply tell RSpec to tell Rails not to manage fixtures and cleaning database.
123+
124+
```ruby
125+
RSpec.configure do |config|
126+
config.use_active_record = false # is true by default
127+
end
128+
```
129+
130+
### Fixtures path
131+
132+
The generator will provide the default path to the fixture, but it is possible to change it:
133+
```ruby
134+
RSpec.configure do |config|
135+
config.fixture_paths = Rails.root.join('some/dir') # Rails.root.join('spec/fixtures') by default
136+
end
137+
```
138+
139+
### Instantiated fixtures
140+
141+
If you want to have your fixtures available as an instance variable in the example, you could use `use_instantiated_fixtures` option:
142+
143+
```ruby
144+
RSpec.configure do |config|
145+
config.use_instantiated_fixtures = true # false, by default
146+
end
147+
148+
RSpec.describe Thing, type: :model do
149+
fixtures :things
150+
151+
it "instantiates fixtures" do
152+
expect(@things["one"]).to eq(@one)
153+
end
154+
end
155+
```
156+
157+
### Global fixtures
158+
159+
Sometimes it is required to have some fixture in each example, and it's possible to do this via `global_fixtures` setting:
160+
161+
```ruby
162+
RSpec.configure do |config|
163+
config.global_fixtures = [:things]
164+
end
165+
166+
RSpec.describe Thing, type: :model do
167+
it "inserts fixture" do
168+
expect(things(:one)).to be_a(Thing)
169+
end
170+
end
171+
```
172+
173+
### Disabling transactions
174+
175+
If your database does not support transactions, but you still want to use Rails fixtures, it is possible to disable transactions explicitly:
176+
177+
```ruby
178+
RSpec.configure do |config|
179+
config.use_transactional_fixtures = false
180+
end
181+
```

features/Transactions.md

Lines changed: 0 additions & 99 deletions
This file was deleted.

lib/generators/rspec/install/templates/spec/rails_helper.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,11 @@
4545
Rails.root.join('spec/fixtures')
4646
]
4747
48-
# If you're not using ActiveRecord, or you'd prefer not to run each of your
49-
# examples within a transaction, remove the following line or assign false
50-
# instead of true.
48+
# If you'd prefer not to run each of your examples within a transaction,
49+
# remove the following line or assign false instead of true.
5150
config.use_transactional_fixtures = true
5251
53-
# You can uncomment this line to turn off ActiveRecord support entirely.
52+
# You can uncomment this line to turn off ActiveRecord support entirely
5453
# config.use_active_record = false
5554
5655
<% else -%>

0 commit comments

Comments
 (0)