lab_coat 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35fcc39652cc2ab43879a6a513214962c139d6b5751f5a1d4b4e8dba7b72b5e3
4
- data.tar.gz: 5b54b1fb3fcabe26a80b2cdd619308f51b7fc44c6b0b93572550eb3e9a50aedd
3
+ metadata.gz: 0c1c5580cec0ec677137fcbf23a90291fab80564c955a1d8b10fb48903f1f43b
4
+ data.tar.gz: 15540d166b6a0d6098c07979efc8b780ecebb26d5059ac3db1217489e2448e06
5
5
  SHA512:
6
- metadata.gz: 48e1e6af3c47d799f6eecd76dcac57a168da9c1729fc29091742b79ae114d36741a9d72bac6348db0db43a868ee5f75087ced725d270ae55074221a2dc152cd5
7
- data.tar.gz: 6b98f3702d8a8cc8494bc2c9eb5e09e1ecc6aff21d204be93d54485eba4754fa60aa89aa832b2644910e42d6ac42c4cdc683db0ac4d0b49b7c3fe2025b417488
6
+ metadata.gz: c84bde55abd4974f068a802f181552111d847c3eeca07e28046fe3492919ba25a12fdefb10ac547bc22fbce90c1785fa01d73084db769f51ebe55993d52b2cc9
7
+ data.tar.gz: 128f1a14f2a2ec4f370d40d5c5b147d35b26abd75f558d5f19755fb71e82cfe8aad4de8673835a82c9b12f21088642ea346aa3bba4d29cdcc7d334a15b4622db
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.1.4] - Unreleased
2
+ - Remove the arity check, it's not very intuitive
3
+ - Adds a `@context` that gets set at runtime and reset after each run. This is a much simpler way for methods to access a shared runtime context that can be set per `run!`.
4
+
1
5
  ## [0.1.3] - 2024-04-17
2
6
  - `Experiment` now enforces arity at runtime for the `#enabled?`, `control`, and `candidate` methods.
3
7
 
data/README.md CHANGED
@@ -57,7 +57,7 @@ See the [`Experiment`](lib/lab_coat/experiment.rb) class for more details.
57
57
  |`publish!`|This is not _technically_ required, but `Experiments` are not useful unless you can analyze the results. Override this method to record the `Result` however you wish.|
58
58
 
59
59
  > [!IMPORTANT]
60
- > The `#run!` method accepts arbitrary arguments and forwards them to `enabled?`, `control`, and `candidate` in case you need to provide data at runtime. This means the [arity](https://en.wikipedia.org/wiki/Arity) of the three methods needs to be the same. This is enforced by `LabCoat` at runtime.
60
+ > The `#run!` method accepts arbitrary key word arguments and stores them in an instance variable called `@context` in case you need to provide data at runtime. You can access the runtime context via `@context` or `context`. The runtime context **is reset** after each run.
61
61
 
62
62
  #### Additional methods
63
63
 
@@ -88,13 +88,16 @@ You might want to `publish!` all experiments in a consistent way so that you can
88
88
  # application_experiment.rb
89
89
  class ApplicationExperiment < LabCoat::Experiment
90
90
  def publish!(result)
91
- payload = result.to_h.merge(user_id: @user.id)
91
+ payload = result.to_h.merge(
92
+ user_id: @user.id, # e.g. something from the `Experiment` state
93
+ build_number: context.version # e.g. something from the runtime context
94
+ )
92
95
  YourO11yService.track_experiment_result(payload)
93
96
  end
94
97
  end
95
98
  ```
96
99
 
97
- You might have a common way to enable experiments such as a feature flag system and/or common guards you want to enforce application wide. These might come from a mix of services and the `Experiment`'s state.
100
+ You might have a common way to enable experiments such as a feature flag system and/or common guards you want to enforce application wide. These might come from a mix of services, the `Experiment`'s state, or the runtime `context`.
98
101
 
99
102
  ```ruby
100
103
  # application_experiment.rb
@@ -123,6 +126,8 @@ end
123
126
 
124
127
  You don't have to create an `Observation` yourself; that happens automatically when you call `Experiment#run!`. The control and candidate `Observations` are packaged into a `Result` and [passed to `Experiment#publish!`](#publish-the-result).
125
128
 
129
+ The `run!` method accepts arbitrary keyword arguments, to allow you to set runtime context for the specific run of the experiment. You can access this `Hash` via the `context` reader method, or directly via the `@context` instance variable.
130
+
126
131
  |Attribute|Description|
127
132
  |---|---|
128
133
  |`duration`|The duration of the run represented as a `Benchmark::Tms` object.|
@@ -180,14 +185,14 @@ A `Result` represents a single run of an `Experiment`.
180
185
  |`matched?`|Whether or not the `control` and `candidate` match, as defined by `Experiment#compare`|
181
186
  |`to_h`|A hash representation of the `Result`. Useful for publishing and/or reporting.|
182
187
 
183
- The `Result` is passed to your implementation of `#publish!` when an `Experiment` is finished running. The `to_h` method on a Result is a good place to start and might be sufficient for most experiments.
188
+ The `Result` is passed to your implementation of `#publish!` when an `Experiment` is finished running. The `to_h` method on a Result is a good place to start and might be sufficient for most experiments. You might want to `merge` additional data such as the runtime `context` or other state if you find that relevant for analysis.
184
189
 
185
190
  ```ruby
186
191
  # your_experiment.rb
187
192
  def publish!(result)
188
193
  return if result.ignored?
189
194
 
190
- puts result.to_h
195
+ puts result.to_h.merge(run_context: context)
191
196
  end
192
197
  ```
193
198
 
@@ -276,7 +281,7 @@ end
276
281
  ```
277
282
 
278
283
  > [!WARNING]
279
- > Be careful when using `Observation` instances without an `Experiment` set. Some methods like `#publishable_value` and `#slug` depend on an `experiment` and may raise an error when called.
284
+ > Be careful when using `Observation` instances without an `Experiment` set. Some methods like `#publishable_value` and `#slug` depend on an `experiment` and may raise an error or return unexpected values when called without one.
280
285
 
281
286
  ## Development
282
287
 
@@ -3,10 +3,11 @@
3
3
  module LabCoat
4
4
  # A base experiment class meant to be subclassed to define various experiments.
5
5
  class Experiment
6
- attr_reader :name
6
+ attr_reader :name, :context
7
7
 
8
8
  def initialize(name)
9
9
  @name = name
10
+ @context = {}
10
11
  end
11
12
 
12
13
  # Override this method to control whether or not the experiment runs.
@@ -18,13 +19,13 @@ module LabCoat
18
19
  # Override this method to define the existing aka "control" behavior. This method is always run, even when
19
20
  # `enabled?` is false.
20
21
  # @return [Object] Anything.
21
- def control(...)
22
+ def control
22
23
  raise InvalidExperimentError, "`#control` must be implemented in your Experiment class."
23
24
  end
24
25
 
25
26
  # Override this method to define the new aka "candidate" behavior. Only run if the experiment is enabled.
26
27
  # @return [Object] Anything.
27
- def candidate(...)
28
+ def candidate
28
29
  raise InvalidExperimentError, "`#candidate` must be implemented in your Experiment class."
29
30
  end
30
31
 
@@ -59,25 +60,25 @@ module LabCoat
59
60
 
60
61
  # Runs the control and candidate and publishes the result. Always returns the result of `control`.
61
62
  # @param context [Hash] Any data needed at runtime.
62
- def run!(...) # rubocop:disable Metrics/MethodLength
63
- enforce_arity!
63
+ def run!(**context)
64
+ # Set the context for this run.
65
+ @context = context
64
66
 
65
67
  # Run the control and exit early if the experiment is not enabled.
66
- control_obs = Observation.new("control", self) do
67
- control(...)
68
- end
68
+ control_obs = Observation.new("control", self) { control }
69
69
  raised(control_obs) if control_obs.raised?
70
- return control_obs.value unless enabled?(...)
70
+ return control_obs.value unless enabled?
71
71
 
72
- candidate_obs = Observation.new("candidate", self) do
73
- candidate(...)
74
- end
72
+ candidate_obs = Observation.new("candidate", self) { candidate }
75
73
  raised(candidate_obs) if candidate_obs.raised?
76
74
 
77
75
  # Compare and publish the results.
78
76
  result = Result.new(self, control_obs, candidate_obs)
79
77
  publish!(result)
80
78
 
79
+ # Reset the context for this run.
80
+ @context = {}
81
+
81
82
  # Always return the control.
82
83
  control_obs.value
83
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LabCoat
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab_coat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Omkar Moghe
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-18 00:00:00.000000000 Z
11
+ date: 2024-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest