diff --git a/.changeset/rails-flag-definition-cache-provider.md b/.changeset/rails-flag-definition-cache-provider.md new file mode 100644 index 0000000..ffd834c --- /dev/null +++ b/.changeset/rails-flag-definition-cache-provider.md @@ -0,0 +1,5 @@ +--- +"posthog-rails": minor +--- + +Add `config.flag_definition_cache_provider` to the `PostHog.init` block so Rails apps can share feature flag definitions across processes via an external cache (for example Redis). The generated initializer now documents the option. diff --git a/posthog-rails/examples/posthog.rb b/posthog-rails/examples/posthog.rb index 15ff337..7b832d0 100644 --- a/posthog-rails/examples/posthog.rb +++ b/posthog-rails/examples/posthog.rb @@ -132,6 +132,16 @@ # Feature flag request timeout in seconds (default: 3) config.feature_flag_request_timeout_seconds = 3 + # Distributed flag definition cache (optional, for local evaluation) + # Share flag definitions across processes (Puma workers, Sidekiq, pods) via an + # external cache so only one process polls PostHog. Pass any object implementing + # the PostHog::FlagDefinitionCacheProvider interface. A Redis implementation is + # in the posthog-ruby repo under examples/redis_flag_cache.rb. + # config.flag_definition_cache_provider = RedisFlagCache.new( + # Redis.new(url: ENV.fetch('REDIS_URL')), + # service_key: "#{Rails.application.class.module_parent_name.underscore}-#{Rails.env}" + # ) + # Error callback - called when PostHog encounters an error # config.on_error = proc { |status, message| # Rails.logger.error("[PostHog] Error #{status}: #{message}") diff --git a/posthog-rails/lib/generators/posthog/templates/posthog.rb b/posthog-rails/lib/generators/posthog/templates/posthog.rb index 86e0969..b188734 100644 --- a/posthog-rails/lib/generators/posthog/templates/posthog.rb +++ b/posthog-rails/lib/generators/posthog/templates/posthog.rb @@ -134,6 +134,16 @@ # Feature flag request timeout in seconds (default: 3) config.feature_flag_request_timeout_seconds = 3 + # Distributed flag definition cache (optional, for local evaluation) + # Share flag definitions across processes (Puma workers, Sidekiq, pods) via an + # external cache so only one process polls PostHog. Pass any object implementing + # the PostHog::FlagDefinitionCacheProvider interface. A Redis implementation is + # in the posthog-ruby repo under examples/redis_flag_cache.rb. + # config.flag_definition_cache_provider = RedisFlagCache.new( + # Redis.new(url: ENV.fetch('REDIS_URL')), + # service_key: "#{Rails.application.class.module_parent_name.underscore}-#{Rails.env}" + # ) + # Error callback - called when PostHog encounters an error # config.on_error = proc { |status, message| # Rails.logger.error("[PostHog] Error #{status}: #{message}") diff --git a/posthog-rails/lib/posthog/rails/railtie.rb b/posthog-rails/lib/posthog/rails/railtie.rb index dfa4bcd..4dcadc7 100644 --- a/posthog-rails/lib/posthog/rails/railtie.rb +++ b/posthog-rails/lib/posthog/rails/railtie.rb @@ -347,6 +347,16 @@ def before_send=(value) @base_options[:before_send] = value end + # An object implementing the {PostHog::FlagDefinitionCacheProvider} interface so multiple + # Rails processes (Puma workers, Sidekiq, Kubernetes pods) can share flag definitions + # through an external cache instead of each polling PostHog independently. + # + # @param value [Object, nil] + # @return [Object, nil] + def flag_definition_cache_provider=(value) + @base_options[:flag_definition_cache_provider] = value + end + # @return [Hash] Core SDK options suitable for {PostHog::Client.new}. def to_client_options @base_options diff --git a/public_api_snapshot.txt b/public_api_snapshot.txt index 278bd8b..4930d38 100644 --- a/public_api_snapshot.txt +++ b/public_api_snapshot.txt @@ -334,6 +334,7 @@ instance_method PostHog::Rails::InitConfig#before_send=(value) instance_method PostHog::Rails::InitConfig#compress_request=(value) instance_method PostHog::Rails::InitConfig#feature_flag_request_timeout_seconds=(value) instance_method PostHog::Rails::InitConfig#feature_flags_polling_interval=(value) +instance_method PostHog::Rails::InitConfig#flag_definition_cache_provider=(value) instance_method PostHog::Rails::InitConfig#host=(value) instance_method PostHog::Rails::InitConfig#max_queue_size=(value) instance_method PostHog::Rails::InitConfig#on_error=(value) diff --git a/spec/posthog/rails/railtie_spec.rb b/spec/posthog/rails/railtie_spec.rb index c216db9..9be00c6 100644 --- a/spec/posthog/rails/railtie_spec.rb +++ b/spec/posthog/rails/railtie_spec.rb @@ -267,6 +267,39 @@ ) end + it 'passes a flag definition cache provider from the init block to the client' do + stub_request(:get, %r{https://us\.i\.posthog\.com/flags/definitions}) + .to_return(status: 200, body: { flags: [], group_type_mapping: {}, cohorts: {} }.to_json) + provider = double( + 'FlagCache', + flag_definitions: nil, + should_fetch_flag_definitions?: true, + on_flag_definitions_received: nil, + shutdown: nil + ) + + PostHog.init do |config| + config.api_key = 'phc_test' + config.secret_key = 'phs_test' + config.flag_definition_cache_provider = provider + config.test_mode = true + end + + expect(provider).to have_received(:on_flag_definitions_received).with(hash_including(:flags)) + ensure + PostHog.client&.shutdown + end + + it 'validates the flag definition cache provider interface' do + expect do + PostHog.init do |config| + config.api_key = 'phc_test' + config.flag_definition_cache_provider = Object.new + config.test_mode = true + end + end.to raise_error(ArgumentError, /flag_definitions/) + end + it 'uses the Rails user agent for event and feature flag requests' do batch_request = stub_request(:post, 'https://us.i.posthog.com/batch/').to_return(status: 200, body: '{}') flags_request = stub_request(:post, 'https://us.i.posthog.com/flags/?v=2')