From 5b1fc4389120ec4d3ffeefe573e456771fc81815 Mon Sep 17 00:00:00 2001 From: Italo Matos Date: Wed, 19 Nov 2025 10:39:56 -0300 Subject: [PATCH] Refactor: Replace ConfigSingleton with direct instance variables Remove custom ConfigSingleton implementation that was causing ActiveSupport::Configurable deprecation warnings in Rails 8.2+. Changes: - Remove ConfigSingleton base class and its metaprogramming - Implement config_accessor using direct instance variable access - Convert config_group pattern to explicit nested classes - Maintain all existing validators and validation logic - Preserve public API compatibility - Update method syntax from blocks to keyword arguments All configuration behavior remains the same, but without relying on deprecated ActiveSupport::Configurable patterns. --- lib/intercom-rails/config.rb | 167 +++++++++++++++++------------------ 1 file changed, 82 insertions(+), 85 deletions(-) diff --git a/lib/intercom-rails/config.rb b/lib/intercom-rails/config.rb index c6dfe2f..cea33c9 100644 --- a/lib/intercom-rails/config.rb +++ b/lib/intercom-rails/config.rb @@ -2,62 +2,8 @@ module IntercomRails - class ConfigSingleton - - def self.config_accessor(*args, &block) - config_reader(*args) - config_writer(*args, &block) - end - - def self.meta_class - class << self; self end - end - - def self.config_reader(name) - meta_class.send(:define_method, name) do - instance_variable_get("@#{name}") - end - end - - def self.config_writer(name, &block) - meta_class.send(:define_method, "#{name}=") do |value| - validate(name, value, block) - instance_variable_set("@#{name}", value) - end - end - - def self.config_group(name, &block) - camelized_name = name.to_s.classify - group = self.const_set(camelized_name, Class.new(self)) - - meta_class.send(:define_method, name) do - group - end - - group.send(:instance_variable_set, :@underscored_class_name, name) - group.instance_eval(&block) - end - - private - - def self.validate(name, value, block) - return unless block - args = [value] - if block.arity > 1 - field_name = underscored_class_name ? "#{underscored_class_name}.#{name}" : name - args << field_name - end - block.call(*args) - end - - def self.underscored_class_name - @underscored_class_name - end - - end - - class Config < ConfigSingleton - + class Config + # Validators (moved outside of class << self for accessibility) CUSTOM_DATA_VALIDATOR = Proc.new do |custom_data, field_name| case custom_data when Hash @@ -79,7 +25,7 @@ class Config < ConfigSingleton end IS_ARAY_OF_PROC_VALIDATOR = Proc.new do |data, field_name| - raise ArgumentError, "#{field_name} data should be a proc or an array of proc" unless data.all? { |value| value.kind_of?(Proc)} + raise ArgumentError, "#{field_name} data should be a proc or an array of proc" unless data.all? { |value| value.kind_of?(Proc)} end IS_PROC_OR_ARRAY_OF_PROC_VALIDATOR = Proc.new do |data, field_name| @@ -90,69 +36,120 @@ class Config < ConfigSingleton end end - def self.reset! - to_reset = self.constants.map {|c| const_get c} - to_reset << self + class << self - to_reset.each do |configer| - configer.instance_variables.each do |var| - configer.send(:remove_instance_variable, var) + # Helper to create accessor with validation + def config_accessor(name, validator: nil) + attr_name = :"@#{name}" + + # Define getter + define_singleton_method(name) do + instance_variable_get(attr_name) + end + + # Define setter with validation + define_singleton_method("#{name}=") do |value| + if validator + field_name = @underscored_class_name ? "#{@underscored_class_name}.#{name}" : name.to_s + args = validator.arity > 1 ? [value, field_name] : [value] + validator.call(*args) + end + instance_variable_set(attr_name, value) end end + + def reset! + to_reset = [self] + constants.select { |c| const_get(c).is_a?(Class) && const_get(c) < Config }.map { |c| const_get(c) } + + to_reset.each do |configer| + configer.instance_variables.each do |var| + configer.send(:remove_instance_variable, var) + end + end + end + + def api_key=(*) + warn "Setting an Intercom API key is no longer supported; remove the `config.api_key = ...` line from config/initializers/intercom.rb" + end end + # Main configuration options config_accessor :app_id config_accessor :session_duration config_accessor :api_secret config_accessor :library_url - config_accessor :enabled_environments, &ARRAY_VALIDATOR + config_accessor :enabled_environments, validator: ARRAY_VALIDATOR config_accessor :include_for_logged_out_users config_accessor :hide_default_launcher config_accessor :api_base config_accessor :encrypted_mode - def self.api_key=(*) - warn "Setting an Intercom API key is no longer supported; remove the `config.api_key = ...` line from config/initializers/intercom.rb" - end + # User configuration group + class User < Config + @underscored_class_name = :user - config_group :user do - config_accessor :current, &IS_PROC_OR_ARRAY_OF_PROC_VALIDATOR - config_accessor :exclude_if, &IS_PROC_VALIDATOR - config_accessor :model, &IS_PROC_VALIDATOR - config_accessor :lead_attributes, &ARRAY_VALIDATOR - config_accessor :custom_data, &CUSTOM_DATA_VALIDATOR + config_accessor :current, validator: IS_PROC_OR_ARRAY_OF_PROC_VALIDATOR + config_accessor :exclude_if, validator: IS_PROC_VALIDATOR + config_accessor :model, validator: IS_PROC_VALIDATOR + config_accessor :lead_attributes, validator: ARRAY_VALIDATOR + config_accessor :custom_data, validator: CUSTOM_DATA_VALIDATOR def self.company_association=(*) warn "Setting a company association is no longer supported; remove the `config.user.company_association = ...` line from config/initializers/intercom.rb" end end - config_group :company do - config_accessor :current, &IS_PROC_VALIDATOR - config_accessor :exclude_if, &IS_PROC_VALIDATOR - config_accessor :plan, &IS_PROC_VALIDATOR - config_accessor :monthly_spend, &IS_PROC_VALIDATOR - config_accessor :custom_data, &CUSTOM_DATA_VALIDATOR + # Company configuration group + class Company < Config + @underscored_class_name = :company + + config_accessor :current, validator: IS_PROC_VALIDATOR + config_accessor :exclude_if, validator: IS_PROC_VALIDATOR + config_accessor :plan, validator: IS_PROC_VALIDATOR + config_accessor :monthly_spend, validator: IS_PROC_VALIDATOR + config_accessor :custom_data, validator: CUSTOM_DATA_VALIDATOR end - config_group :inbox do + # Inbox configuration group + class Inbox < Config + @underscored_class_name = :inbox + config_accessor :counter # Keep this for backwards compatibility config_accessor :custom_activator - config_accessor :style do |value| + config_accessor :style, validator: Proc.new { |value| raise ArgumentError, "inbox.style must be one of :default or :custom" unless [:default, :custom].include?(value) - end + } end - config_group :jwt do + # JWT configuration group + class Jwt < Config + @underscored_class_name = :jwt + config_accessor :enabled config_accessor :expiry - config_accessor :signed_user_fields do |value| + config_accessor :signed_user_fields, validator: Proc.new { |value| unless value.nil? || (value.kind_of?(Array) && value.all? { |v| v.kind_of?(Symbol) || v.kind_of?(String) }) raise ArgumentError, "jwt.signed_user_fields must be an array of symbols or strings" end - end + } + end + + # Methods to access configuration groups + def self.user + User end + def self.company + Company + end + + def self.inbox + Inbox + end + + def self.jwt + Jwt + end end end