once-ler 0.0.2 → 0.0.3

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.
data/README.md CHANGED
@@ -30,17 +30,25 @@ Change a slow `before` to `before(:once)` to speed it up.
30
30
 
31
31
  Change a slow `let` (or `let!`) to `let_once` to speed it up.
32
32
 
33
+ ### subject_once(...) { ... }
34
+
35
+ Change a slow `subject` (or `subject!`) to `subject_once` to speed it up.
36
+
33
37
  ## Ambitious usage
34
38
 
35
39
  If you're feeling bold, you can automatically speed up all
36
- `let`s/`before(:each)`s in an example group:
40
+ `let`s/`before`s in an example group:
37
41
 
38
42
  ```ruby
39
43
  describe "something" do
40
44
  onceler!
41
- let(:foo) { ... } # behaves like let_once
42
- before { ... } # behaves like before(:once)
43
- before(:all) { ... } # no change here though
45
+
46
+ let(:foo) { ... } # behaves like let_once
47
+ before { ... } # behaves like before(:once)
48
+
49
+ # but if you need explict eaches, you can still do them:
50
+ let_each(:foo) { ... }
51
+ before(:each) { ... }
44
52
  end
45
53
  ```
46
54
 
@@ -66,6 +74,9 @@ of activerecord callbacks/inserts/updates.
66
74
 
67
75
  ## Caveats
68
76
 
77
+ * If you are doing anything database-y, you need to use transactional
78
+ tests (either via `use_transactional_fixtures=true`, or something like
79
+ [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner))
69
80
  * Your once'd blocks should have no side effects other than database
70
81
  statements, return values, and instance variables.
71
82
  * Your return values and instance variables need to be able to handle a
@@ -1,39 +1,8 @@
1
1
  module Onceler
2
2
  module AmbitiousHelpers
3
- def before_once?(type)
4
- super || type == :each || type.nil?
5
- end
6
-
7
- def let(name, &block)
8
- let_once(name, &block)
9
- end
10
-
11
- # TODO NamedSubjectPreventSuper
12
- def subject(name = nil, &block)
13
- subject_once(name, &block)
14
- end
15
-
16
- # remove auto-before'ing of ! methods, since we memoize our own way
17
- def let!(name, &block)
18
- let(name, &block)
19
- end
20
-
21
- def subject!(name = nil, &block)
22
- subject(name, &block)
23
- end
24
-
25
- # make sure we have access to subsequently added methods when
26
- # recording (not just `lets'). note that this really only works
27
- # for truly functional methods with no external dependencies. e.g.
28
- # methods that add stubs or set instance variables will not work
29
- # while recording
30
- def method_added(method_name)
31
- return if method_name == @current_let_once
32
- onceler = onceler(:create)
33
- proxy = onceler.helper_proxy ||= new
34
- onceler.helper_methods[method_name] ||= Proc.new do |*args|
35
- proxy.send method_name, *args
36
- end
3
+ # make :once the default behavior for before/let/etc.
4
+ def once_scope?(scope)
5
+ super || scope.nil?
37
6
  end
38
7
  end
39
8
  end
@@ -1,5 +1,4 @@
1
1
  require "onceler/ambitious_helpers"
2
- require "onceler/around_all"
3
2
  require "onceler/recorder"
4
3
 
5
4
  module Onceler
@@ -13,15 +12,15 @@ module Onceler
13
12
  end
14
13
 
15
14
  module ClassMethods
16
- include AroundAll
17
-
18
15
  def let_once(name, &block)
16
+ raise ArgumentError, "wrong number of arguments (0 for 1)" if name.nil?
19
17
  raise "#let or #subject called without a block" if block.nil?
20
18
  onceler(:create)[name] = block
21
19
  @current_let_once = name
22
20
  define_method(name) { onceler[name] }
23
21
  end
24
22
 
23
+ # TODO NamedSubjectPreventSuper
25
24
  def subject_once(name = nil, &block)
26
25
  name ||= :subject
27
26
  let_once(name, &block)
@@ -32,12 +31,35 @@ module Onceler
32
31
  onceler(:create) << block
33
32
  end
34
33
 
35
- def before_once?(type)
36
- type == :once
34
+ def once_scope?(scope)
35
+ scope == :once
36
+ end
37
+
38
+ # add second scope argument to explicitly differentiate between
39
+ # :each / :once
40
+ [:let, :let!, :subject, :subject!].each do |method|
41
+ once_method = (method.to_s.sub(/!\z/, '') + "_once").to_sym
42
+ define_method(method) do |name = nil, scope = nil, &block|
43
+ if once_scope?(scope)
44
+ send once_method, name, &block
45
+ else
46
+ super name, &block
47
+ end
48
+ end
49
+ end
50
+
51
+ # set up let_each, etc.
52
+ [:let, :let!, :subject, :subject!].each do |method|
53
+ each_method = method.to_s
54
+ bang = each_method.sub!(/!\z/, '')
55
+ each_method = (each_method + "_each" + (bang ? "!" : "")).to_sym
56
+ define_method(each_method) do |name = nil, &block|
57
+ send method, name, &block
58
+ end
37
59
  end
38
60
 
39
61
  def before(*args, &block)
40
- if before_once?(args.first)
62
+ if once_scope?(args.first)
41
63
  before_once(&block)
42
64
  else
43
65
  super(*args, &block)
@@ -57,6 +79,20 @@ module Onceler
57
79
  Recorder.new(parent_onceler)
58
80
  end
59
81
 
82
+ # make sure we have access to subsequently added methods when
83
+ # recording (not just `lets'). note that this really only works
84
+ # for truly functional methods with no external dependencies. e.g.
85
+ # methods that add stubs or set instance variables will not work
86
+ # while recording
87
+ def method_added(method_name)
88
+ return if method_name == @current_let_once
89
+ return if !@onceler
90
+ proxy = onceler.helper_proxy ||= new
91
+ onceler.helper_methods[method_name] ||= Proc.new do |*args|
92
+ proxy.send method_name, *args
93
+ end
94
+ end
95
+
60
96
  private
61
97
 
62
98
  def parent_onceler
@@ -65,18 +101,17 @@ module Onceler
65
101
  end
66
102
 
67
103
  def add_onceler_hooks!
68
- around_all do |group|
69
- # TODO: configurable transaction fu (say, if you have multiple
70
- # conns)
71
- ActiveRecord::Base.transaction(requires_new: true) do
72
- group.onceler.record!
73
- group.run_examples
74
- raise ActiveRecord::Rollback
75
- end
104
+ prepend_before(:all) do |group|
105
+ group.onceler.record!
76
106
  end
107
+
108
+ after(:all) do |group|
109
+ group.onceler.reset!
110
+ end
111
+
77
112
  # only the outer-most group needs to do this
78
113
  unless parent_onceler
79
- register_hook :append, :before, :each do
114
+ before :each do
80
115
  onceler.replay_into!(self)
81
116
  end
82
117
  end
@@ -1,4 +1,5 @@
1
1
  require "onceler/blank_tape"
2
+ require "active_record"
2
3
 
3
4
  module Onceler
4
5
  class Recorder
@@ -24,6 +25,7 @@ module Onceler
24
25
  end
25
26
 
26
27
  def record!
28
+ begin_transactions!
27
29
  @tape = @parent ? @parent.tape.copy(mixins) : BlankTape.new(mixins)
28
30
  proxy_recordable_methods!
29
31
 
@@ -38,6 +40,10 @@ module Onceler
38
40
  @data = Marshal.dump(@tape.__data)
39
41
  end
40
42
 
43
+ def reset!
44
+ rollback_transactions!
45
+ end
46
+
41
47
  def proxy_recordable_methods!
42
48
  # the proxy is used to run non-recordable methods that may be called
43
49
  # by ones are recording. since the former could in turn call more of
@@ -92,6 +98,57 @@ module Onceler
92
98
  instance.instance_variable_set(key, value)
93
99
  end
94
100
  end
101
+
102
+ # TODO: configurable transaction fu (say, if you have multiple
103
+ # conns)
104
+ def transaction_classes
105
+ [ActiveRecord::Base]
106
+ end
107
+
108
+ def begin_transactions!
109
+ transaction_classes.each do |klass|
110
+ begin_transaction(klass.connection)
111
+ end
112
+ end
113
+
114
+ def rollback_transactions!
115
+ transaction_classes.each do |klass|
116
+ rollback_transaction(klass.connection)
117
+ end
118
+ end
119
+
120
+ if ActiveRecord::VERSION::MAJOR >= 4
121
+ def begin_transaction(conn)
122
+ conn.begin_transaction requires_new: true
123
+ end
124
+
125
+ def rollback_transaction(conn)
126
+ conn.rollback_transaction
127
+ end
128
+ else
129
+ def begin_transaction(klass)
130
+ unless conn.instance_variable_get(:@_current_transaction_records)
131
+ conn.instance_variable_set(:@_current_transaction_records, [])
132
+ end
133
+ if conn.open_transactions == 0
134
+ conn.begin_db_transaction
135
+ else
136
+ conn.create_savepoint
137
+ end
138
+ conn.increment_open_transactions
139
+ end
140
+
141
+ def begin_transaction(klass)
142
+ conn.decrement_open_transactions
143
+ if conn.open_transactions == 0
144
+ conn.rollback_db_transaction
145
+ conn.send :rollback_transaction_records, true
146
+ else
147
+ conn.rollback_to_savepoint
148
+ conn.send :rollback_transaction_records, false
149
+ end
150
+ end
151
+ end
95
152
  end
96
153
 
97
154
  class Recording
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: once-ler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-26 00:00:00.000000000 Z
12
+ date: 2014-06-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -53,7 +53,6 @@ files:
53
53
  - README.md
54
54
  - lib/once-ler.rb
55
55
  - lib/onceler/ambitious_helpers.rb
56
- - lib/onceler/around_all.rb
57
56
  - lib/onceler/basic_helpers.rb
58
57
  - lib/onceler/blank_tape.rb
59
58
  - lib/onceler/configuration.rb
@@ -1,32 +0,0 @@
1
- # adapted from https://gist.github.com/myronmarston/2005175
2
- require 'delegate'
3
- require 'fiber'
4
-
5
- module Onceler
6
- module AroundAll
7
- class FiberAwareGroup < SimpleDelegator
8
- def run_examples
9
- Fiber.yield
10
- end
11
-
12
- def to_proc
13
- proc { run_examples }
14
- end
15
- end
16
-
17
- def around_all(&block)
18
- fibers = []
19
- prepend_before(:all) do |group|
20
- fiber = Fiber.new(&block)
21
- fibers << fiber
22
- fiber.resume(FiberAwareGroup.new(group))
23
- end
24
-
25
- after(:all) do |group|
26
- fiber = fibers.pop
27
- fiber.resume if fiber.alive?
28
- end
29
- end
30
- end
31
- end
32
-