rails-settings Documentation

repository·master·Indexed 21 days ago

https://github.com/ledermann/rails-settings

A Ruby gem for managing ActiveRecord settings stored as serialized Hashes in a separate database table. It supports namespaces, default values (including dynamic Procs), custom validation objects, and polymorphic scoping. Key features include the `has_settings` macro, `RailsSettings::Base` for setting manipulation, and specialized query scopes like `with_settings_for` and `without_settings_for` to filter records based on their configuration.

Tokens
3.6K
Snippets
13
Records
14
Agent score
77%

What's inside rails-settings

  1. Install ledermann-rails-settings

    master

    To install the gem, add it to your Gemfile and run bundle. Then, generate and run the necessary database migration to create the settings table.

    Requirements:

    • Ruby 3.2 or newer
    • Rails 6.1 or newer (including Rails 8.0)
    # Gemfile
    gem 'ledermann-rails-settings'
    # Terminal
    rails g rails_settings:migration
    rake db:migrate
  2. How setting objects handle dynamic method access

    master

    The RailsSettings::SettingObject class uses Ruby's method_missing to provide a dynamic API for interacting with settings. This allows you to get and set individual keys within a setting hash as if they were direct methods on the object.

    • Getters: Calling a method name that matches a key in the settings hash (or a key defined in the target class's default_settings) returns the value. If the value is not explicitly set, it falls back to the default value defined in the target class.
    • Setters: Calling a method with a trailing = (e.g., key=value) updates the specific key within the settings hash. If you pass nil to a setter, the key is deleted from the hash.

    Note that the object uses regex patterns REGEX_SETTER (/\A([a-z]\w*)=\Z/i) and REGEX_GETTER (/\A([a-z]\w*)\Z/i) to identify these dynamic calls.

    # Example of dynamic getter and setter
    # Assuming 'site_settings' is a SettingObject instance
    site_settings.theme = 'dark' # Calls _set_value via REGEX_SETTER
    puts site_settings.theme     # Calls _get_value via REGEX_GETTER
    
    # Setting to nil deletes the key
    site_settings.theme = nil
  3. Default values and dynamic defaults in RailsSettings

    master

    When a setting value is not explicitly stored in the database, RailsSettings::SettingObject attempts to retrieve a default value from the default_settings defined on the target class.

    If the default value is a Proc (responds to .call), it is executed with the target object passed as an argument. This allows for dynamic default values that depend on the instance of the target class.

    To ensure data integrity, retrieved values are deep-duplicated using Marshal to prevent accidental mutation of the default values.

  4. Query models using setting scopes

    master

    The gem provides scope methods to filter ActiveRecord collections based on whether they have settings defined.

    • with_settings: Returns all records having any setting.
    • without_settings: Returns all records without any settings.
    • with_settings_for(:key): Returns records having a setting for the specific key.
    • without_settings_for(:key): Returns records without settings for the specific key.
    # Find all users with any settings
    User.with_settings
    
    # Find all users without any settings
    User.without_settings
    
    # Find all users with a 'calendar' setting
    User.with_settings_for(:calendar)
    
    # Find all users without a 'calendar' setting
    User.without_settings_for(:calendar)
  5. Eager load settings to prevent N+1 queries

    master

    When querying multiple records that have settings, use .includes(:setting_objects) to eager load the settings and avoid N+1 query performance issues.

    # Eager load setting_objects when querying many users
    users = User.includes(:setting_objects)
  6. Get settings values

    master

    Access setting values directly from the setting object. If a value has been set, it returns the current value; otherwise, it returns the defined default.

    user = User.find(1)
    
    # Returns the custom value
    user.settings(:dashboard).theme
    # => 'black'
    
    # Returns the default value if not set
    user.settings(:dashboard).view
    # => 'monthly'
  7. Delete settings

    master

    To delete a setting (resetting it to its default or nil), you can use .update! with a nil value or assign nil to the attribute and call .save! on the parent model.

    user = User.find(1)
    
    # Using update!
    user.settings(:dashboard).update! :theme => nil
    
    # Using assignment
    user.settings(:dashboard).view = nil
    user.settings(:dashboard).save!
  8. Define settings in ActiveRecord models

    master

    Use the has_settings macro within your ActiveRecord models to define setting keys. You can provide default values using a block or use a simplified syntax if no defaults are required.

    Basic definition with defaults

    class User < ActiveRecord::Base
      has_settings do |s|
        s.key :dashboard, :defaults => { :theme => 'blue', :view => 'monthly', :filter => false }
        s.key :calendar,  :defaults => { :scope => 'company'}
      end
    end

    Simplified definition (no defaults)

    class User < ActiveRecord::Base
      has_settings :dashboard, :calendar
    end

    Custom Setting Objects for Validations

    By default, settings use RailsSettings::SettingObject. You can provide a custom class to implement validations for your settings.

    class Project < ActiveRecord::Base
      has_settings :info, :class_name => 'ProjectSettingObject'
    end
    
    class ProjectSettingObject < RailsSettings::SettingObject
      validate do
        unless self.owner_name.present? && self.owner_name.is_a?(String)
          errors.add(:base, "Owner name is missing")
        end
      end
    end

    Persistent settings for multiple definitions

    If you need to define settings separately for the same model (e.g., across different concerns), use the persistent: true option to ensure they are stored in the same settings record.

    module UserDashboardConcern
      extend ActiveSupport::Concern
    
      included do
        has_settings persistent: true do |s|
          s.key :dashboard
        end
      end
    end
    
    class User < ActiveRecord::Base
      has_settings persistent: true do |s|
        s.key :calendar
      end
    end
    class User < ActiveRecord::Base
      has_settings do |s|
        s.key :dashboard, :defaults => { :theme => 'blue', :view => 'monthly', :filter => false }
      end
    end
  9. Set and update settings

    master

    You can modify settings by accessing the setting object via the .settings(:key) method and assigning values, or by using the .update! method. Remember to call .save! if you are using direct assignment to persist changes to the database.

    user = User.find(1)
    
    # Option 1: Direct assignment
    user.settings(:dashboard).theme = 'black'
    user.save! # Required to persist changes
    
    # Option 2: Using update!
    user.settings(:calendar).update! :scope => 'all', :display => 'daily'
  10. Filter records using RailsSettings query scopes

    master

    The RailsSettings::Scopes module provides ActiveRecord scopes to filter models based on whether they have specific settings associated with them. These scopes allow you to perform efficient SQL joins to find records that either possess or lack certain settings.

    Available Scopes

    • with_settings: Returns only records that have at least one setting entry.
    • with_settings_for(var): Returns only records that have a setting for the specific variable name provided. var must be a Symbol.
    • without_settings: Returns records that have no settings entries at all.
    • without_settings_for(var): Returns records that do not have a setting for the specific variable name provided. var must be a Symbol.
    # Example usage on a model that includes RailsSettings::Scopes
    
    # Find users who have any settings defined
    User.with_settings
    
    # Find users who have a specific 'theme' setting
    User.with_settings_for(:theme)
    
    # Find users who have no settings at all
    User.without_settings
    
    # Find users who specifically lack a 'notifications_enabled' setting
    User.without_settings_for(:notifications_enabled)
  11. Enable settings in an ActiveRecord model using `has_settings`

    master

    To add settings capabilities to an ActiveRecord model, call the has_settings macro on the class. This method initializes the settings configuration for that model and enables scope-based settings management. It is designed to be called within your model definition.

    class SiteConfig < ActiveRecord::Base
      has_settings
    end
  12. Configure settings using the RailsSettings::Configuration DSL

    master

    The RailsSettings::Configuration class is used to define setting keys and their default values for a specific class. You can initialize it by passing the target class, an optional hash of options, and a list of keys, or by providing a block for a more expressive DSL.

    Initialization Options

    • klass: The class to which the settings will be attached (required).
    • options[:persistent]: If true, ensures the class has a default_settings attribute. If false, it still ensures default_settings is available via class_attribute.
    • options[:class_name]: An optional string to override the default setting object class name (defaults to 'RailsSettings::SettingObject').

    Defining Keys

    When using the block syntax, you call key(name, options) to define a setting.

    • name: Must be a Symbol.
    • options[:defaults]: A hash of default values for that specific key. Keys in this hash are stringified and frozen.

    Note: If you do not provide a block, the constructor will attempt to register all provided keys as settings without specific default options.

    # Example using the block DSL
    RailsSettings::Configuration.new(MySettingsClass, persistent: true) do |config|
      config.key(:site_name, defaults: { value: 'My App' })
      config.key(:maintenance_mode, defaults: { enabled: false })
    end
    
    # Example using the positional arguments syntax
    RailsSettings::Configuration.new(MySettingsClass, persistent: true, :site_name, :maintenance_mode)