featurevisor 0.1.1 → 0.3.0

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: 7b51aa1bea2c05bb11437fc38b488f959de46b190e0c0ed7c9c6f39ee53f978e
4
- data.tar.gz: 2a6062fcb9aafddb52dad685b781c8049f64bc74df04aa9b5267de878884f46f
3
+ metadata.gz: 42c8ec3e5bf6293eeae189365b0383588a8cf7fdd5a5ee249a6fd44a2676403c
4
+ data.tar.gz: ce5794865b3dd5a057b9e0841814dab1c276b52237f230e23da6317f8dea3f70
5
5
  SHA512:
6
- metadata.gz: af2413b5ca231e827ae57ad580c1ea0f31e117e229fed5161c5cd87d66629865c5cddf3c978f84dea3659427809c80530a297160cc39ea207b5fb6e5fdf7bcf8
7
- data.tar.gz: 8edc93ec41d1a73ef115fd8e739aafb7d1e3db6c1fb8199334a1608f8e8a4a75f574b10f2b16bc58de019fe0b8b1540cc4e21f51ea010292bb4650173725df86
6
+ metadata.gz: 41aefa056806a97bc1f5bf13d847007fd4b38fb176f97d1a09868434644fda2ed8cd58698ad9f5002a6ef5d30f7fc105accf7d505eb41fb71abe28a5bda6e47c
7
+ data.tar.gz: e4376d46699b8fe730d4dc4f73890fd43c6e879c446bc484dc6a47cc6b2c31f715af41b83b5f6189f9b8eeed3fd61b48213ec589855d909fe7ebccb702c04303
data/README.md CHANGED
@@ -41,6 +41,7 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
41
41
  - [Close](#close)
42
42
  - [CLI usage](#cli-usage)
43
43
  - [Test](#test)
44
+ - [Test against local monorepo's example-1](#test-against-local-monorepos-example-1)
44
45
  - [Benchmark](#benchmark)
45
46
  - [Assess distribution](#assess-distribution)
46
47
  - [Development](#development)
@@ -49,6 +50,8 @@ This SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 proje
49
50
  - [Releasing](#releasing)
50
51
  - [License](#license)
51
52
 
53
+ <!-- FEATUREVISOR_DOCS_BEGIN -->
54
+
52
55
  ## Installation
53
56
 
54
57
  Add this line to your application's Gemfile:
@@ -81,7 +84,9 @@ require 'json'
81
84
  # Fetch datafile from URL
82
85
  datafile_url = 'https://cdn.yoursite.com/datafile.json'
83
86
  response = Net::HTTP.get_response(URI(datafile_url))
84
- datafile_content = JSON.parse(response.body)
87
+
88
+ # Parse JSON with symbolized keys (required)
89
+ datafile_content = JSON.parse(response.body, symbolize_names: true)
85
90
 
86
91
  # Create SDK instance
87
92
  f = Featurevisor.create_instance(
@@ -89,6 +94,19 @@ f = Featurevisor.create_instance(
89
94
  )
90
95
  ```
91
96
 
97
+ **Important**: When parsing JSON datafiles, you must use `symbolize_names: true` to ensure proper key handling by the SDK.
98
+
99
+ Alternatively, you can pass a JSON string directly and the SDK will parse it automatically:
100
+
101
+ ```ruby
102
+ # Option 1: Parse JSON yourself (recommended)
103
+ datafile_content = JSON.parse(json_string, symbolize_names: true)
104
+ f = Featurevisor.create_instance(datafile: datafile_content)
105
+
106
+ # Option 2: Pass JSON string directly (automatic parsing)
107
+ f = Featurevisor.create_instance(datafile: json_string)
108
+ ```
109
+
92
110
  ## Evaluation types
93
111
 
94
112
  We can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):
@@ -334,9 +352,16 @@ f.set_sticky({
334
352
  You may also initialize the SDK without passing `datafile`, and set it later on:
335
353
 
336
354
  ```ruby
355
+ # Parse with symbolized keys before setting
356
+ datafile_content = JSON.parse(json_string, symbolize_names: true)
337
357
  f.set_datafile(datafile_content)
358
+
359
+ # Or pass JSON string directly for automatic parsing
360
+ f.set_datafile(json_string)
338
361
  ```
339
362
 
363
+ **Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.
364
+
340
365
  ### Updating datafile
341
366
 
342
367
  You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile_set) event that you can listen and react to accordingly.
@@ -359,7 +384,7 @@ require 'json'
359
384
  def update_datafile(f, datafile_url)
360
385
  loop do
361
386
  sleep(5 * 60) # 5 minutes
362
-
387
+
363
388
  begin
364
389
  response = Net::HTTP.get_response(URI(datafile_url))
365
390
  datafile_content = JSON.parse(response.body)
@@ -659,10 +684,27 @@ Additional options that are available:
659
684
  ```bash
660
685
  $ bundle exec featurevisor test \
661
686
  --projectDirectoryPath="/absolute/path/to/your/featurevisor/project" \
662
- --quiet|verbose \
687
+ --quiet|--verbose \
663
688
  --onlyFailures \
664
689
  --keyPattern="myFeatureKey" \
665
- --assertionPattern="#1"
690
+ --assertionPattern="#1" \
691
+ --with-scopes \
692
+ --with-tags
693
+ ```
694
+
695
+ `--with-scopes` and `--with-tags` make the Ruby test runner build scoped/tagged datafiles in memory (via `npx featurevisor build --json`) and evaluate matching assertions against those exact datafiles.
696
+
697
+ If an assertion references `scope` and `--with-scopes` is not provided, the runner still evaluates the assertion by merging that scope's configured context into the assertion context (without building scoped datafiles).
698
+
699
+ For compatibility, camelCase aliases are also supported: `--withScopes` and `--withTags`.
700
+
701
+ ### Test against local monorepo's example-1
702
+
703
+ ```bash
704
+ $ cd /absolute/path/to/featurevisor-ruby
705
+ $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1
706
+ $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-scopes
707
+ $ bundle exec featurevisor test --projectDirectoryPath=./monorepo/examples/example-1 --with-tags
666
708
  ```
667
709
 
668
710
  ### Benchmark
@@ -694,6 +736,8 @@ $ bundle exec featurevisor assess-distribution \
694
736
  --n=1000
695
737
  ```
696
738
 
739
+ <!-- FEATUREVISOR_DOCS_END -->
740
+
697
741
  ## Development
698
742
 
699
743
  ### Setting up
@@ -715,7 +759,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
715
759
  - Push commit to `main` branch
716
760
  - Wait for CI to complete
717
761
  - Tag the release with the version number
718
- - This will trigger a new release to RubyGems
762
+ - This will trigger a new release to [RubyGems](https://rubygems.org/gems/featurevisor)
719
763
 
720
764
  ## License
721
765
 
data/bin/cli.rb CHANGED
@@ -7,7 +7,7 @@ module FeaturevisorCLI
7
7
  attr_accessor :command, :assertion_pattern, :context, :environment, :feature,
8
8
  :key_pattern, :n, :only_failures, :quiet, :variable, :variation,
9
9
  :verbose, :inflate, :show_datafile, :schema_version, :project_directory_path,
10
- :populate_uuid
10
+ :populate_uuid, :with_scopes, :with_tags
11
11
 
12
12
  def initialize
13
13
  @n = 1000
@@ -86,6 +86,14 @@ module FeaturevisorCLI
86
86
  options.schema_version = v
87
87
  end
88
88
 
89
+ opts.on("--with-scopes", "--withScopes", "Test scoped assertions against scoped datafiles") do
90
+ options.with_scopes = true
91
+ end
92
+
93
+ opts.on("--with-tags", "--withTags", "Test tagged assertions against tagged datafiles") do
94
+ options.with_tags = true
95
+ end
96
+
89
97
  opts.on("--projectDirectoryPath=PATH", "Project directory path") do |v|
90
98
  options.project_directory_path = v
91
99
  end
@@ -58,8 +58,8 @@ module FeaturevisorCLI
58
58
 
59
59
  # Initialize evaluation counters
60
60
  flag_evaluations = {
61
- "enabled" => 0,
62
- "disabled" => 0
61
+ enabled: 0,
62
+ disabled: 0
63
63
  }
64
64
  variation_evaluations = {}
65
65
 
@@ -78,9 +78,9 @@ module FeaturevisorCLI
78
78
  # Evaluate flag
79
79
  flag_evaluation = instance.is_enabled(@options.feature, context_copy)
80
80
  if flag_evaluation
81
- flag_evaluations["enabled"] += 1
81
+ flag_evaluations[:enabled] += 1
82
82
  else
83
- flag_evaluations["disabled"] += 1
83
+ flag_evaluations[:disabled] += 1
84
84
  end
85
85
 
86
86
  # Evaluate variation if feature has variations
@@ -147,7 +147,7 @@ module FeaturevisorCLI
147
147
  end
148
148
 
149
149
  begin
150
- JSON.parse(stdout)
150
+ JSON.parse(stdout, symbolize_names: true)
151
151
  rescue JSON::ParserError => e
152
152
  puts "Error: Failed to parse datafile JSON: #{e.message}"
153
153
  exit 1
@@ -160,27 +160,13 @@ module FeaturevisorCLI
160
160
  end
161
161
 
162
162
  def create_instance(datafile)
163
- # Convert datafile to proper format for the SDK
164
- symbolized_datafile = symbolize_keys(datafile)
165
-
166
163
  # Create SDK instance
167
164
  Featurevisor.create_instance(
168
- datafile: symbolized_datafile,
165
+ datafile: datafile,
169
166
  log_level: get_logger_level
170
167
  )
171
168
  end
172
169
 
173
- def symbolize_keys(obj)
174
- case obj
175
- when Hash
176
- obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
177
- when Array
178
- obj.map { |item| symbolize_keys(item) }
179
- else
180
- obj
181
- end
182
- end
183
-
184
170
  def get_logger_level
185
171
  if @options.verbose
186
172
  "debug"
@@ -120,7 +120,7 @@ module FeaturevisorCLI
120
120
 
121
121
  # Parse the JSON output
122
122
  begin
123
- JSON.parse(datafile_output)
123
+ JSON.parse(datafile_output, symbolize_names: true)
124
124
  rescue JSON::ParserError => e
125
125
  puts "Error: Failed to parse datafile JSON: #{e.message}"
126
126
  puts "Command output: #{datafile_output}"
@@ -143,16 +143,13 @@ module FeaturevisorCLI
143
143
  end
144
144
 
145
145
  def create_instance(datafile)
146
- # Convert string keys to symbols for the SDK
147
- symbolized_datafile = symbolize_keys(datafile)
148
-
149
146
  # Create a real Featurevisor instance
150
147
  instance = Featurevisor.create_instance(
151
148
  log_level: get_logger_level
152
149
  )
153
150
 
154
151
  # Explicitly set the datafile
155
- instance.set_datafile(symbolized_datafile)
152
+ instance.set_datafile(datafile)
156
153
 
157
154
  instance
158
155
  end
@@ -167,17 +164,6 @@ module FeaturevisorCLI
167
164
  end
168
165
  end
169
166
 
170
- def symbolize_keys(obj)
171
- case obj
172
- when Hash
173
- obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
174
- when Array
175
- obj.map { |item| symbolize_keys(item) }
176
- else
177
- obj
178
- end
179
- end
180
-
181
167
  def benchmark_feature_flag(instance, feature_key, context, n)
182
168
  start_time = Time.now
183
169