Skip to content

Commit 34a87e0

Browse files
Luiz Kowalskiluizkowalski
authored andcommitted
Releasing v0.2.1
1 parent 7667767 commit 34a87e0

File tree

9 files changed

+111
-12
lines changed

9 files changed

+111
-12
lines changed

.github/workflows/ruby.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ on:
88

99
jobs:
1010
test:
11-
1211
runs-on: ubuntu-latest
1312
strategy:
1413
matrix:
@@ -35,7 +34,7 @@ jobs:
3534
- uses: ruby/setup-ruby@v1
3635
with:
3736
ruby-version: ${{ matrix.ruby }}
38-
bundler-cache: true # runs bundle install and caches installed gems automatically
37+
bundler-cache: true
3938
bundler: ${{ env.BUNDLER_VERSION || 'latest' }}
4039
- name: Rubocop
4140
run: bundle exec rubocop -D

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.2.1
4+
5+
* Added `ConcurrentRails::Testing` tool
6+
* Dropped support for Ruby 2.5
7+
* Added deprecation warning for `ConcurrentRails::Future`
8+
* Wrapped `touch` and `wait` on Rails' executor as well
9+
310
## 0.2.0
411

512
* Added delayed futures
@@ -30,15 +37,15 @@
3037

3138
## 0.1.4
3239

33-
* Fixed `ConcurrentRails::Promises`'s `future` factory so it handles parameters and block correctly
40+
* Fixed `ConcurrentRails::Promises`'s `future` factory so it handles parameters and blocks correctly
3441

3542
## 0.1.3
3643

3744
* Dropped support for Ruby and Rails versions that reached EOL
3845

3946
## 0.1.2
4047

41-
* Fixed depencies for development
48+
* Fixed dependencies for development
4249

4350
## 0.1.1
4451

Gemfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
concurrent_rails (0.2.0)
4+
concurrent_rails (0.2.1)
55
rails (>= 5.2)
66

77
GEM
@@ -128,7 +128,7 @@ GEM
128128
rake (13.0.3)
129129
regexp_parser (2.1.1)
130130
rexml (3.2.5)
131-
rubocop (1.16.0)
131+
rubocop (1.17.0)
132132
parallel (~> 1.10)
133133
parser (>= 3.0.0.0)
134134
rainbow (>= 2.2.2, < 4.0)
@@ -157,7 +157,7 @@ GEM
157157
tzinfo (2.0.4)
158158
concurrent-ruby (~> 1.0)
159159
unicode-display_width (2.0.0)
160-
websocket-driver (0.7.4)
160+
websocket-driver (0.7.5)
161161
websocket-extensions (>= 0.1.0)
162162
websocket-extensions (0.1.5)
163163
zeitwerk (2.4.2)
@@ -174,4 +174,4 @@ DEPENDENCIES
174174
sqlite3 (~> 1.4)
175175

176176
BUNDLED WITH
177-
2.2.19
177+
2.2.20

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ All of these callbacks have a bang version (e.g. `on_fulfillment!`). The bang ve
8383

8484
### (Deprecated) Future
8585

86-
`ConcurrentRails::Future` will execute your code in a separated thread and you can check the progress of it whenever you need it. When the task is ready, you can access the result with `#result` function:
86+
`ConcurrentRails::Future` will execute your code in a separate thread and you can check the progress of it whenever you need it. When the task is ready, you can access the result with `#result` function:
8787

8888
```ruby
8989
irb(main):001:0> future = ConcurrentRails::Future.new do
@@ -173,6 +173,37 @@ irb(main):007:0> multi.errors
173173

174174
It is worth mention that a failed proc will return `nil`.
175175

176+
## Testing
177+
If you are using RSpec, you will notice that it might not play well with threads. ActiveRecord opens a database connection for every thread and since RSpec tests are wrapped in a transaction, by the time you promise tries to access something on the database, for example, a user, gems like Database Cleaner probably already triggered and deleted the user, resulting in `ActiveRecord::RecordNotFound` errors. You have a couple of solutions like disable transactional fixtures if you are using it or update the Database Cleaner strategy (that will result in much slower tests).
178+
Since none of these solutions were satisfactory to me, I created `ConcurrentRails::Testing` with two strategies: `immediate!` and `fake!`. When you wrap a Promise's `future` with `immediate!`, the executor gets replaced from `:io` to `:immediate`. It still returns a promise anyway. This is not the case with `fake!` strategy: it executes the task outside the `ConcurrentRails` engine and returns whatever `.value` would return:
179+
180+
`immediate!` strategy:
181+
```ruby
182+
irb(main):001:1* result = ConcurrentRails::Testing.immediate! do
183+
irb(main):002:1* ConcurrentRails::Promises.future { 42 }
184+
irb(main):003:0> end
185+
=>
186+
#<ConcurrentRails::Promises:0x000000013e5fc870
187+
...
188+
irb(main):004:0> result.class
189+
=> ConcurrentRails::Promises # <-- Still a `ConcurrentRails::Promises` class
190+
irb(main):005:0> result.executor
191+
=> :immediate # <-- default executor (:io) gets replaced
192+
```
193+
194+
`fake!` strategy:
195+
196+
```ruby
197+
irb(main):001:1* result = ConcurrentRails::Testing.fake! do
198+
irb(main):002:1* ConcurrentRails::Promises.future { 42 }
199+
irb(main):003:0> end
200+
=> 42 # <-- yields the task but does not return a Promise
201+
irb(main):004:0> result.class
202+
=> Integer
203+
```
204+
205+
## Further reading
206+
176207
For more information on how Futures work and how Rails handle multithread check these links:
177208

178209
[Future documentation](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/future.md)
@@ -184,7 +215,7 @@ For more information on how Futures work and how Rails handle multithread check
184215
Add this line to your application's Gemfile:
185216

186217
```ruby
187-
gem 'concurrent_rails', '~> 0.2.0'
218+
gem 'concurrent_rails', '~> 0.2.1'
188219
```
189220

190221
And then execute:

lib/concurrent_rails.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
require 'concurrent_rails/multi'
55
require 'concurrent_rails/promises'
66
require 'concurrent_rails/railtie'
7+
require 'concurrent_rails/testing'
78
require 'concurrent_rails/version'

lib/concurrent_rails/promises.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def wait(timeout = nil)
6464

6565
delegate :state, :reason, :rejected?, :resolved?, :fulfilled?, to: :instance
6666

67+
attr_reader :executor
68+
6769
private
6870

6971
def rails_wrapped(&block)
@@ -76,6 +78,6 @@ def permit_concurrent_loads(&block)
7678
end
7779
end
7880

79-
attr_reader :executor, :instance
81+
attr_reader :instance
8082
end
8183
end

lib/concurrent_rails/testing.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module ConcurrentRails
4+
class Testing
5+
class << self
6+
attr_reader :execution_mode
7+
8+
%i[immediate fake].each do |exec_method|
9+
define_method("#{exec_method}!") do |&task|
10+
@execution_mode = exec_method
11+
result = task.call
12+
@execution_mode = :real
13+
14+
result
15+
end
16+
17+
define_method("#{exec_method}?") do
18+
execution_mode == exec_method
19+
end
20+
end
21+
22+
module TestingFuture
23+
def future(*args, &task)
24+
if ConcurrentRails::Testing.immediate?
25+
future_on(:immediate, *args, &task)
26+
elsif ConcurrentRails::Testing.fake?
27+
yield
28+
else
29+
super
30+
end
31+
end
32+
end
33+
34+
ConcurrentRails::Promises.extend(TestingFuture)
35+
end
36+
end
37+
end

lib/concurrent_rails/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module ConcurrentRails
4-
VERSION = '0.2.0'
4+
VERSION = '0.2.1'
55
end

test/immediate_adapter_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class ImmediateAdapterTest < ActiveSupport::TestCase
6+
test 'should override :io executor when using `immediate!`' do
7+
result = ConcurrentRails::Testing.immediate! do
8+
ConcurrentRails::Promises.future { 42 }
9+
end
10+
11+
assert_equal(:immediate, result.executor)
12+
assert_instance_of(ConcurrentRails::Promises, result)
13+
end
14+
15+
test 'should not return a promise when using `fake!`' do
16+
result = ConcurrentRails::Testing.fake! do
17+
ConcurrentRails::Promises.future { 42 }
18+
end
19+
20+
assert_equal(42, result)
21+
end
22+
end

0 commit comments

Comments
 (0)