flagsmith 2.0.0 → 3.0.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +4 -1
  4. data/.rubocop_todo.yml +0 -1
  5. data/.ruby-version +1 -0
  6. data/Gemfile.lock +35 -6
  7. data/LICENCE +4 -5
  8. data/README.md +8 -64
  9. data/Rakefile +5 -1
  10. data/example/.env.development +5 -0
  11. data/example/.env.test +4 -0
  12. data/example/.gitignore +5 -0
  13. data/example/.hanamirc +3 -0
  14. data/example/.rspec +2 -0
  15. data/example/Gemfile +25 -0
  16. data/example/Gemfile.lock +269 -0
  17. data/example/README.md +29 -0
  18. data/example/Rakefile +9 -0
  19. data/example/apps/web/application.rb +162 -0
  20. data/example/apps/web/config/routes.rb +1 -0
  21. data/example/apps/web/controllers/.gitkeep +0 -0
  22. data/example/apps/web/controllers/home/index.rb +32 -0
  23. data/example/apps/web/templates/application.html.slim +7 -0
  24. data/example/apps/web/templates/home/index.html.slim +10 -0
  25. data/example/apps/web/views/application_layout.rb +7 -0
  26. data/example/apps/web/views/home/index.rb +29 -0
  27. data/example/config/boot.rb +2 -0
  28. data/example/config/environment.rb +17 -0
  29. data/example/config/initializers/.gitkeep +0 -0
  30. data/example/config/initializers/flagsmith.rb +9 -0
  31. data/example/config/puma.rb +15 -0
  32. data/example/config.ru +3 -0
  33. data/example/spec/example/entities/.gitkeep +0 -0
  34. data/example/spec/example/mailers/.gitkeep +0 -0
  35. data/example/spec/example/repositories/.gitkeep +0 -0
  36. data/example/spec/features_helper.rb +12 -0
  37. data/example/spec/spec_helper.rb +103 -0
  38. data/example/spec/support/.gitkeep +0 -0
  39. data/example/spec/support/capybara.rb +8 -0
  40. data/example/spec/web/controllers/.gitkeep +0 -0
  41. data/example/spec/web/controllers/home/index_spec.rb +9 -0
  42. data/example/spec/web/features/.gitkeep +0 -0
  43. data/example/spec/web/views/application_layout_spec.rb +10 -0
  44. data/example/spec/web/views/home/index_spec.rb +10 -0
  45. data/lib/flagsmith/engine/core.rb +88 -0
  46. data/lib/flagsmith/engine/environments/models.rb +61 -0
  47. data/lib/flagsmith/engine/features/models.rb +173 -0
  48. data/lib/flagsmith/engine/identities/models.rb +115 -0
  49. data/lib/flagsmith/engine/organisations/models.rb +28 -0
  50. data/lib/flagsmith/engine/projects/models.rb +31 -0
  51. data/lib/flagsmith/engine/segments/constants.rb +41 -0
  52. data/lib/flagsmith/engine/segments/evaluator.rb +68 -0
  53. data/lib/flagsmith/engine/segments/models.rb +121 -0
  54. data/lib/flagsmith/engine/utils/hash_func.rb +34 -0
  55. data/lib/flagsmith/hash_slice.rb +12 -0
  56. data/lib/flagsmith/sdk/analytics_processor.rb +39 -0
  57. data/lib/flagsmith/sdk/api_client.rb +47 -0
  58. data/lib/flagsmith/sdk/config.rb +91 -0
  59. data/lib/flagsmith/sdk/errors.rb +9 -0
  60. data/lib/flagsmith/sdk/instance_methods.rb +137 -0
  61. data/lib/flagsmith/sdk/intervals.rb +24 -0
  62. data/lib/flagsmith/sdk/models/flag.rb +62 -0
  63. data/lib/flagsmith/sdk/models/flags/collection.rb +105 -0
  64. data/lib/flagsmith/sdk/pooling_manager.rb +31 -0
  65. data/lib/flagsmith/version.rb +5 -0
  66. data/lib/flagsmith.rb +79 -101
  67. metadata +104 -6
  68. data/.gitignore +0 -57
  69. data/flagsmith.gemspec +0 -22
data/lib/flagsmith.rb CHANGED
@@ -1,121 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'faraday'
4
+ require 'faraday/retry'
4
5
  require 'faraday_middleware'
5
6
 
6
- # Ruby client for flagsmith.com
7
- class Flagsmith
8
- attr_reader :flagsmith_api
9
-
10
- def initialize(opts = {})
11
- @opts = determine_opts(opts)
12
-
13
- @flagsmith_api = Faraday.new(url: @opts[:url]) do |faraday|
14
- faraday.headers['Accept'] = 'application/json'
15
- faraday.headers['Content-Type'] = 'application/json'
16
- faraday.headers['x-environment-key'] = @opts[:api_key]
17
- faraday.response :json
18
- # TODO: add timeout adjustment here
19
- faraday.adapter Faraday.default_adapter
7
+ # Hash#slice was added in ruby version 2.5
8
+ # This is the patch to use slice in earler versions
9
+ require 'flagsmith/hash_slice'
10
+
11
+ require 'flagsmith/sdk/analytics_processor'
12
+ require 'flagsmith/sdk/api_client'
13
+ require 'flagsmith/sdk/config'
14
+ require 'flagsmith/sdk/errors'
15
+ require 'flagsmith/sdk/intervals'
16
+ require 'flagsmith/sdk/pooling_manager'
17
+ require 'flagsmith/sdk/models/flag'
18
+ require 'flagsmith/sdk/models/flags/collection'
19
+ require 'flagsmith/sdk/instance_methods'
20
+
21
+ require 'flagsmith/engine/core'
22
+
23
+ # no-doc
24
+ module Flagsmith
25
+ # Ruby client for flagsmith.com
26
+ class Client
27
+ extend Forwardable
28
+ include Flagsmith::SDK::InstanceMethods
29
+ include Flagsmith::Engine::Core
30
+ # A Flagsmith client.
31
+ #
32
+ # Provides an interface for interacting with the Flagsmith http API.
33
+ # Basic Usage::
34
+ #
35
+ # flagsmith = Flagsmith::Client.new(environment_key: '<your API key>')
36
+ #
37
+ # environment_flags = flagsmith.get_environment_flags
38
+ # feature_enabled = environment_flags.is_feature_enabled('foo')
39
+ # feature_value = identity_flags.get_feature_value('foo')
40
+ #
41
+ # identity_flags = flagsmith.get_identity_flags('identifier', 'foo': 'bar')
42
+ # feature_enabled_for_identity = identity_flags.is_feature_enabled('foo')
43
+ # feature_value_for_identity = identity_flags.get_feature_value('foo')
44
+
45
+ # Available Configs.
46
+ #
47
+ # :environment_key, :api_url, :custom_headers, :request_timeout_seconds, :enable_local_evaluation,
48
+ # :environment_refresh_interval_seconds, :retries, :enable_analytics, :default_flag_handler
49
+ # You can see full description in the Flagsmith::Config
50
+
51
+ attr_reader :config, :environment
52
+
53
+ delegate Flagsmith::Config::OPTIONS => :@config
54
+
55
+ def initialize(config)
56
+ @_mutex = Mutex.new
57
+ @config = Flagsmith::Config.new(config)
58
+
59
+ api_client
60
+ analytics_processor
61
+ environment_data_polling_manager
20
62
  end
21
- end
22
63
 
23
- def get_flags(user_id = nil)
24
- if user_id.nil?
25
- res = @flagsmith_api.get('flags/')
26
- flags = transform_flags(res.body).select { |flag| flag[:segment].nil? }
27
- flags_to_hash(flags)
28
- else
29
- res = @flagsmith_api.get("identities/?identifier=#{user_id}")
30
- flags_to_hash(transform_flags(res.body['flags']))
64
+ def api_client
65
+ @api_client ||= Flagsmith::ApiClient.new(@config)
31
66
  end
32
- end
33
-
34
- def feature_enabled?(feature, user_id = nil, default = false)
35
- flag = get_flags(user_id)[normalize_key(feature)]
36
- return default if flag.nil?
37
-
38
- flag[:enabled]
39
- end
40
-
41
- def get_value(key, user_id = nil, default = nil)
42
- flag = get_flags(user_id)[normalize_key(key)]
43
- return default if flag.nil?
44
-
45
- flag[:value]
46
- end
47
67
 
48
- def set_trait(user_id, trait, value)
49
- raise StandardError, 'user_id cannot be nil' if user_id.nil?
68
+ def analytics_processor
69
+ return nil unless @config.enable_analytics?
50
70
 
51
- trait = {
52
- identity: { identifier: user_id },
53
- trait_key: normalize_key(trait),
54
- trait_value: value
55
- }
56
- res = @flagsmith_api.post('traits/', trait.to_json)
57
- res.body
58
- end
71
+ @analytics_processor ||=
72
+ Flagsmith::AnalyticsProcessor.new(
73
+ api_client: api_client,
74
+ timeout: request_timeout_seconds
75
+ )
76
+ end
59
77
 
60
- def get_traits(user_id)
61
- return {} if user_id.nil?
78
+ def environment_data_polling_manager
79
+ return nil unless @config.local_evaluation?
62
80
 
63
- res = @flagsmith_api.get("identities/?identifier=#{user_id}")
64
- traits_to_hash(res.body)
65
- end
81
+ update_environment
66
82
 
67
- # def remove_trait(user_id, trait_id)
68
- # # Request URL: https://api.flagsmith.com/api/v1/environments/API_KEY/identities/12345/traits/54321/
69
- # # Request Method: DELETE
70
- # end
71
-
72
- def transform_flags(flags)
73
- flags.map do |flag|
74
- {
75
- name: flag['feature']['name'],
76
- enabled: flag['enabled'],
77
- value: flag['feature_state_value'],
78
- segment: flag['feature_segment']
79
- }
83
+ @environment_data_polling_manager ||= Flagsmith::EnvironmentDataPollingManager.new(
84
+ self, environment_refresh_interval_seconds
85
+ ).tap(&:start)
80
86
  end
81
- end
82
87
 
83
- def flags_to_hash(flags)
84
- result = {}
85
- flags.each do |flag|
86
- key = normalize_key(flag.delete(:name))
87
- result[key] = flag
88
+ # Updates the environment state for local flag evaluation.
89
+ # You only need to call this if you wish to bypass environment_refresh_interval_seconds.
90
+ def update_environment
91
+ @_mutex.synchronize { @environment = environment_from_api }
88
92
  end
89
- result
90
- end
91
93
 
92
- def traits_to_hash(user_flags)
93
- result = {}
94
- user_flags['traits']&.each do |t|
95
- key = normalize_key(t['trait_key'])
96
- result[key] = t['trait_value']
94
+ def environment_from_api
95
+ environment_data = api_client.get(@config.environment_url).body
96
+ Flagsmith::Engine::Environment.build(environment_data)
97
97
  end
98
- result
99
- end
100
-
101
- def normalize_key(key)
102
- key.to_s.downcase
103
- end
104
-
105
- def determine_opts(opts)
106
- opts = { api_key: opts } if opts.is_a? String
107
-
108
- {
109
- api_key: opts[:api_key] || self.class.api_key,
110
- url: opts[:url] || self.class.api_url
111
- }
112
- end
113
-
114
- def self.api_key
115
- ENV['FLAGSMITH_API_KEY']
116
- end
117
-
118
- def self.api_url
119
- ENV.fetch('FLAGSMITH_URL', 'https://api.flagsmith.com/api/v1/')
120
98
  end
121
99
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flagsmith
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Stuart
8
8
  - Brian Moelk
9
9
  autorequire:
10
- bindir: bin
10
+ bindir: exe
11
11
  cert_chain: []
12
- date: 2021-01-04 00:00:00.000000000 Z
12
+ date: 2022-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: pry
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: rake
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +123,34 @@ dependencies:
109
123
  - - ">="
110
124
  - !ruby/object:Gem::Version
111
125
  version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: faraday-retry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: semantic
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :runtime
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
112
154
  description: Ruby Client for Flagsmith. Ship features with confidence using feature
113
155
  flags and remote config. Host yourself or use our hosted version at https://flagsmith.com
114
156
  email:
@@ -118,16 +160,72 @@ executables: []
118
160
  extensions: []
119
161
  extra_rdoc_files: []
120
162
  files:
121
- - ".gitignore"
163
+ - ".rspec"
122
164
  - ".rubocop.yml"
123
165
  - ".rubocop_todo.yml"
166
+ - ".ruby-version"
124
167
  - Gemfile
125
168
  - Gemfile.lock
126
169
  - LICENCE
127
170
  - README.md
128
171
  - Rakefile
129
- - flagsmith.gemspec
172
+ - example/.env.development
173
+ - example/.env.test
174
+ - example/.gitignore
175
+ - example/.hanamirc
176
+ - example/.rspec
177
+ - example/Gemfile
178
+ - example/Gemfile.lock
179
+ - example/README.md
180
+ - example/Rakefile
181
+ - example/apps/web/application.rb
182
+ - example/apps/web/config/routes.rb
183
+ - example/apps/web/controllers/.gitkeep
184
+ - example/apps/web/controllers/home/index.rb
185
+ - example/apps/web/templates/application.html.slim
186
+ - example/apps/web/templates/home/index.html.slim
187
+ - example/apps/web/views/application_layout.rb
188
+ - example/apps/web/views/home/index.rb
189
+ - example/config.ru
190
+ - example/config/boot.rb
191
+ - example/config/environment.rb
192
+ - example/config/initializers/.gitkeep
193
+ - example/config/initializers/flagsmith.rb
194
+ - example/config/puma.rb
195
+ - example/spec/example/entities/.gitkeep
196
+ - example/spec/example/mailers/.gitkeep
197
+ - example/spec/example/repositories/.gitkeep
198
+ - example/spec/features_helper.rb
199
+ - example/spec/spec_helper.rb
200
+ - example/spec/support/.gitkeep
201
+ - example/spec/support/capybara.rb
202
+ - example/spec/web/controllers/.gitkeep
203
+ - example/spec/web/controllers/home/index_spec.rb
204
+ - example/spec/web/features/.gitkeep
205
+ - example/spec/web/views/application_layout_spec.rb
206
+ - example/spec/web/views/home/index_spec.rb
130
207
  - lib/flagsmith.rb
208
+ - lib/flagsmith/engine/core.rb
209
+ - lib/flagsmith/engine/environments/models.rb
210
+ - lib/flagsmith/engine/features/models.rb
211
+ - lib/flagsmith/engine/identities/models.rb
212
+ - lib/flagsmith/engine/organisations/models.rb
213
+ - lib/flagsmith/engine/projects/models.rb
214
+ - lib/flagsmith/engine/segments/constants.rb
215
+ - lib/flagsmith/engine/segments/evaluator.rb
216
+ - lib/flagsmith/engine/segments/models.rb
217
+ - lib/flagsmith/engine/utils/hash_func.rb
218
+ - lib/flagsmith/hash_slice.rb
219
+ - lib/flagsmith/sdk/analytics_processor.rb
220
+ - lib/flagsmith/sdk/api_client.rb
221
+ - lib/flagsmith/sdk/config.rb
222
+ - lib/flagsmith/sdk/errors.rb
223
+ - lib/flagsmith/sdk/instance_methods.rb
224
+ - lib/flagsmith/sdk/intervals.rb
225
+ - lib/flagsmith/sdk/models/flag.rb
226
+ - lib/flagsmith/sdk/models/flags/collection.rb
227
+ - lib/flagsmith/sdk/pooling_manager.rb
228
+ - lib/flagsmith/version.rb
131
229
  homepage: https://flagsmith.com
132
230
  licenses: []
133
231
  metadata: {}
@@ -146,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
244
  - !ruby/object:Gem::Version
147
245
  version: '0'
148
246
  requirements: []
149
- rubygems_version: 3.0.3
247
+ rubygems_version: 3.0.3.1
150
248
  signing_key:
151
249
  specification_version: 4
152
250
  summary: Flagsmith - Ship features with confidence
data/.gitignore DELETED
@@ -1,57 +0,0 @@
1
- .vscode/*
2
- !.vscode/settings.json
3
- !.vscode/tasks.json
4
- !.vscode/launch.json
5
- !.vscode/extensions.json
6
-
7
-
8
- *.gem
9
- *.rbc
10
- /.config
11
- /coverage/
12
- /InstalledFiles
13
- /pkg/
14
- /spec/reports/
15
- /spec/examples.txt
16
- /test/tmp/
17
- /test/version_tmp/
18
- /tmp/
19
-
20
- # Used by dotenv library to load environment variables.
21
- # .env
22
-
23
- ## Specific to RubyMotion:
24
- .dat*
25
- .repl_history
26
- build/
27
- *.bridgesupport
28
- build-iPhoneOS/
29
- build-iPhoneSimulator/
30
-
31
- ## Specific to RubyMotion (use of CocoaPods):
32
- #
33
- # We recommend against adding the Pods directory to your .gitignore. However
34
- # you should judge for yourself, the pros and cons are mentioned at:
35
- # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36
- #
37
- # vendor/Pods/
38
-
39
- ## Documentation cache and generated files:
40
- /.yardoc/
41
- /_yardoc/
42
- /doc/
43
- /rdoc/
44
-
45
- ## Environment normalization:
46
- /.bundle/
47
- /vendor/bundle
48
- /lib/bundler/man/
49
-
50
- # for a library or gem, you might want to ignore these files since the code is
51
- # intended to run in multiple environments; otherwise, check them in:
52
- # Gemfile.lock
53
- # .ruby-version
54
- # .ruby-gemset
55
-
56
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
57
- .rvmrc
data/flagsmith.gemspec DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Gem::Specification.new do |spec|
4
- spec.required_ruby_version = '>= 2.4.0'
5
- spec.name = 'flagsmith'
6
- spec.version = '2.0.0'
7
- spec.authors = ['Tom Stuart', 'Brian Moelk']
8
- spec.email = ['tom@solidstategroup.com', 'bmoelk@gmail.com']
9
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
10
-
11
- spec.summary = 'Flagsmith - Ship features with confidence'
12
- spec.description = 'Ruby Client for Flagsmith. Ship features with confidence using feature flags and remote config. Host yourself or use our hosted version at https://flagsmith.com'
13
- spec.homepage = 'https://flagsmith.com'
14
-
15
- spec.add_development_dependency 'bundler'
16
- spec.add_development_dependency 'gem-release'
17
- spec.add_development_dependency 'rake'
18
- spec.add_development_dependency 'rspec'
19
- spec.add_development_dependency 'rubocop'
20
- spec.add_dependency 'faraday'
21
- spec.add_dependency 'faraday_middleware'
22
- end