{"id":13879289,"url":"https://github.com/huacnlee/rails-settings-cached","last_synced_at":"2025-05-13T00:12:32.012Z","repository":{"id":1328456,"uuid":"1274040","full_name":"huacnlee/rails-settings-cached","owner":"huacnlee","description":"Global settings for your Rails application.","archived":false,"fork":false,"pushed_at":"2024-12-10T05:54:27.000Z","size":414,"stargazers_count":1099,"open_issues_count":8,"forks_count":204,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-13T00:12:24.228Z","etag":null,"topics":["config","configuration","rails","setting"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"ledermann/rails-settings","license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/huacnlee.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-01-20T08:41:50.000Z","updated_at":"2025-05-09T11:13:36.000Z","dependencies_parsed_at":"2025-01-28T11:01:29.537Z","dependency_job_id":null,"html_url":"https://github.com/huacnlee/rails-settings-cached","commit_stats":{"total_commits":306,"total_committers":56,"mean_commits":5.464285714285714,"dds":0.3464052287581699,"last_synced_commit":"d4d86af25b9d03d4c8bdb3f2596a3983171b8770"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huacnlee%2Frails-settings-cached","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huacnlee%2Frails-settings-cached/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huacnlee%2Frails-settings-cached/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huacnlee%2Frails-settings-cached/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/huacnlee","download_url":"https://codeload.github.com/huacnlee/rails-settings-cached/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253843219,"owners_count":21972874,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config","configuration","rails","setting"],"created_at":"2024-08-06T08:02:16.322Z","updated_at":"2025-05-13T00:12:31.985Z","avatar_url":"https://github.com/huacnlee.png","language":"Ruby","funding_links":[],"categories":["Ruby","Gems"],"sub_categories":["Articles"],"readme":"# Rails Settings Cached\n\nThe best solution for store global settings in Rails applications.\n\nThis gem will make managing a table of а global key, value pairs easy. Think of it like a global Hash stored in your database, that uses simple ActiveRecord like methods for manipulation. Keep track of any global setting that you don't want to hard code into your Rails application.\n\n[![Gem Version](https://badge.fury.io/rb/rails-settings-cached.svg)](https://rubygems.org/gems/rails-settings-cached) [![build](https://github.com/huacnlee/rails-settings-cached/workflows/build/badge.svg)](https://github.com/huacnlee/rails-settings-cached/actions?query=workflow%3Abuild)\n\n## Installation\n\nEdit your Gemfile:\n\n```bash\n$ bundle add rails-settings-cached\n```\n\nGenerate your settings:\n\n```bash\n$ rails g settings:install\n\n# Or use a custom name:\n$ rails g settings:install AppConfig\n```\n\nYou will get `app/models/setting.rb`\n\n```rb\nclass Setting \u003c RailsSettings::Base\n  # cache_prefix { \"v1\" }\n\n  scope :application do\n    field :app_name, default: \"Rails Settings\", validates: { presence: true, length: { in: 2..20 } }\n    field :host, default: \"http://example.com\", readonly: true\n    field :default_locale, default: \"zh-CN\", validates: { presence: true, inclusion: { in: %w[zh-CN en jp] } }, option_values: %w[en zh-CN jp], help_text: \"Bla bla ...\"\n    field :admin_emails, type: :array, default: %w[admin@rubyonrails.org]\n\n    # lambda default value\n    field :welcome_message, type: :string, default: -\u003e { \"welcome to #{self.app_name}\" }, validates: { length: { maximum: 255 } }\n    # Override array separator, default: /[\\n,]/ split with \\n or comma.\n    field :tips, type: :array, separator: /[\\n]+/\n  end\n\n  scope :limits do\n    field :user_limits, type: :integer, default: 20\n    field :exchange_rate, type: :float, default: 0.123\n    field :captcha_enable, type: :boolean, default: true\n  end\n\n  field :notification_options, type: :hash, default: {\n    send_all: true,\n    logging: true,\n    sender_email: \"foo@bar.com\"\n  }\n\n  field :readonly_item, type: :integer, default: 100, readonly: true\nend\n```\n\nYou must use the `field` method to statement the setting keys, otherwise you can't use it.\n\nThe `scope` method allows you to group the keys for admin UI.\n\nNow just put that migration in the database with:\n\n```bash\n$ rails db:migrate\n```\n\n## Usage\n\nThe syntax is easy. First, let's create some settings to keep track of:\n\n```ruby\nirb \u003e Setting.host\n\"http://example.com\"\nirb \u003e Setting.app_name\n\"Rails Settings\"\nirb \u003e Setting.app_name = \"Rails Settings Cached\"\nirb \u003e Setting.app_name\n\"Rails Settings Cached\"\n\nirb \u003e Setting.user_limits\n20\nirb \u003e Setting.user_limits = \"30\"\nirb \u003e Setting.user_limits\n30\nirb \u003e Setting.user_limits = 45\nirb \u003e Setting.user_limits\n45\n\nirb \u003e Setting.captcha_enable\n1\nirb \u003e Setting.captcha_enable?\ntrue\nirb \u003e Setting.captcha_enable = \"0\"\nirb \u003e Setting.captcha_enable\nfalse\nirb \u003e Setting.captcha_enable = \"1\"\nirb \u003e Setting.captcha_enable\ntrue\nirb \u003e Setting.captcha_enable = \"false\"\nirb \u003e Setting.captcha_enable\nfalse\nirb \u003e Setting.captcha_enable = \"true\"\nirb \u003e Setting.captcha_enable\ntrue\nirb \u003e Setting.captcha_enable?\ntrue\n\nirb \u003e Setting.admin_emails\n[\"admin@rubyonrails.org\"]\nirb \u003e Setting.admin_emails = %w[foo@bar.com bar@dar.com]\nirb \u003e Setting.admin_emails\n[\"foo@bar.com\", \"bar@dar.com\"]\nirb \u003e Setting.admin_emails = \"huacnlee@gmail.com,admin@admin.com\\nadmin@rubyonrails.org\"\nirb \u003e Setting.admin_emails\n[\"huacnlee@gmail.com\", \"admin@admin.com\", \"admin@rubyonrails.org\"]\n\nirb \u003e Setting.notification_options\n{\n  send_all: true,\n  logging: true,\n  sender_email: \"foo@bar.com\"\n}\nirb \u003e Setting.notification_options = {\n  sender_email: \"notice@rubyonrails.org\"\n}\nirb \u003e Setting.notification_options\n{\n  sender_email: \"notice@rubyonrails.org\"\n}\n```\n\n### Get defined fields\n\n\u003e version 2.3+\n\n```rb\n# Get all keys\nSetting.keys\n=\u003e [\"app_name\", \"host\", \"default_locale\", \"readonly_item\"]\n\n# Get editable keys\nSetting.editable_keys\n=\u003e [\"app_name\", \"default_locale\"]\n\n# Get readonly keys\nSetting.readonly_keys\n=\u003e [\"host\", \"readonly_item\"]\n\n# Get field\nSetting.get_field(\"host\")\n=\u003e { scope: :application, key: \"host\", type: :string, default: \"http://example.com\", readonly: true }\nSetting.get_field(\"app_name\")\n=\u003e { scope: :application, key: \"app_name\", type: :string, default: \"Rails Settings\", readonly: false }\nSetting.get_field(:user_limits)\n=\u003e { scope: :limits, key: \"user_limits\", type: :integer, default: 20, readonly: false }\n# Get field options\nSetting.get_field(\"default_locale\")[:options]\n=\u003e { option_values: %w[en zh-CN jp], help_text: \"Bla bla ...\" }\n```\n\n### Custom type for setting\n\n\u003e Since: 2.9.0\n\nYou can write your custom field type by under `RailsSettings::Fields` module.\n\n#### For example\n\n```rb\nmodule RailsSettings\n  module Fields\n    class YesNo \u003c ::RailsSettings::Fields::Base\n      def serialize(value)\n        case value\n        when true then \"YES\"\n        when false then \"NO\"\n        else raise StandardError, 'invalid value'\n        end\n      end\n\n      def deserialize(value)\n        case value\n        when \"YES\" then true\n        when \"NO\" then false\n        else nil\n        end\n      end\n    end\n  end\nend\n```\n\nNow you can use `yes_no` type in you setting:\n\n```rb\nclass Setting\n  field :custom_item, type: :yes_no, default: 'YES'\nend\n```\n\n```rb\nirb\u003e Setting.custom_item = 'YES'\nirb\u003e Setting.custom_item\ntrue\nirb\u003e Setting.custom_item = 'NO'\nirb\u003e Setting.custom_item\nfalse\n```\n\n#### Get All defined fields\n\n\u003e version 2.7.0+\n\nYou can use `defined_fields` method to get all defined fields in Setting.\n\n```rb\n# Get editable fields and group by scope\neditable_fields = Setting.defined_fields\n  .select { |field| !field[:readonly] }\n  .group_by { |field| field[:scope] }\n```\n\n## Validations\n\nYou can use `validates` options to special the [Rails Validation](https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates) for fields.\n\n```rb\nclass Setting \u003c RailsSettings::Base\n  # cache_prefix { \"v1\" }\n  field :app_name, default: \"Rails Settings\", validates: { presence: true, length: { in: 2..20 } }\n  field :default_locale, default: \"zh-CN\", validates: { presence: true, inclusion: { in: %w[zh-CN en jp], message: \"is not included in [zh-CN, en, jp]\" } }\nend\n```\n\nNow validate will work on record save:\n\n```rb\nirb\u003e Setting.app_name = \"\"\nActiveRecord::RecordInvalid: (Validation failed: App name can't be blank)\nirb\u003e Setting.app_name = \"Rails Settings\"\n\"Rails Settings\"\nirb\u003e Setting.default_locale = \"zh-TW\"\nActiveRecord::RecordInvalid: (Validation failed: Default locale is not included in [zh-CN, en, jp])\nirb\u003e Setting.default_locale = \"en\"\n\"en\"\n```\n\nValidate by `save` / `valid?` method:\n\n```rb\n\nsetting = Setting.find_or_initialize_by(var: :app_name)\nsetting.value = \"\"\nsetting.valid?\n# =\u003e false\nsetting.errors.full_messages\n# =\u003e [\"App name can't be blank\", \"App name too short (minimum is 2 characters)\"]\n\nsetting = Setting.find_or_initialize_by(var: :default_locale)\nsetting.value = \"zh-TW\"\nsetting.save\n# =\u003e false\nsetting.errors.full_messages\n# =\u003e [\"Default locale is not included in [zh-CN, en, jp]\"]\nsetting.value = \"en\"\nsetting.valid?\n# =\u003e true\n```\n\n## Use Setting in Rails initializing:\n\nIn `version 2.3+` you can use Setting before Rails is initialized.\n\nFor example `config/initializers/devise.rb`\n\n```rb\nDevise.setup do |config|\n  if Setting.omniauth_google_client_id.present?\n    config.omniauth :google_oauth2, Setting.omniauth_google_client_id, Setting.omniauth_google_client_secret\n  end\nend\n```\n\n```rb\nclass Setting \u003c RailsSettings::Base\n  field :omniauth_google_client_id, default: ENV[\"OMNIAUTH_GOOGLE_CLIENT_ID\"]\n  field :omniauth_google_client_secret, default: ENV[\"OMNIAUTH_GOOGLE_CLIENT_SECRET\"]\nend\n```\n\n## Readonly field\n\nYou may also want use Setting before Rails initialize:\n\n```\nconfig/environments/*.rb\n```\n\nIf you want do that do that, the setting field must has `readonly: true`.\n\nFor example:\n\n```rb\nclass Setting \u003c RailsSettings::Base\n  field :mailer_provider, default: (ENV[\"mailer_provider\"] || \"smtp\"), readonly: true\n  field :mailer_options, type: :hash, readonly: true, default: {\n    address: ENV[\"mailer_options.address\"],\n    port: ENV[\"mailer_options.port\"],\n    domain: ENV[\"mailer_options.domain\"],\n    user_name: ENV[\"mailer_options.user_name\"],\n    password: ENV[\"mailer_options.password\"],\n    authentication: ENV[\"mailer_options.authentication\"] || \"login\",\n    enable_starttls_auto: ENV[\"mailer_options.enable_starttls_auto\"]\n  }\nend\n```\n\nconfig/environments/production.rb\n\n```rb\n# You must require_relative directly in Rails 6.1+ in config/environments/production.rb\nrequire_relative \"../../app/models/setting\"\n\nRails.application.configure do\n  config.action_mailer.delivery_method = :smtp\n  config.action_mailer.smtp_settings = Setting.mailer_options.deep_symbolize_keys\nend\n```\n\nTIP: You also can follow this file to rewrite ActionMailer's `mail` method for configuration Mail options from Setting after Rails booted.\n\nhttps://github.com/ruby-china/homeland/blob/main/app/mailers/application_mailer.rb#L19\n\n## Caching flow:\n\n```\nSetting.host -\u003e Check Cache -\u003e Exist - Get value of key for cache -\u003e Return\n                   |\n                Fetch all key and values from DB -\u003e Write Cache -\u003e Get value of key for cache -\u003e return\n                   |\n                Return default value or nil\n```\n\nIn each Setting keys call, we will load the cache/db and save in [ActiveSupport::CurrentAttributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html) to avoid hit cache/db.\n\nEach key update will expire the cache, so do not add some frequent update key.\n\n## Change cache key\n\nSome times you may need to force update cache, now you can use `cache_prefix`\n\n```ruby\nclass Setting \u003c RailsSettings::Base\n  cache_prefix { \"you-prefix\" }\n  ...\nend\n```\n\nIn testing, you need add `Setting.clear_cache` for each Test case:\n\n```rb\nclass ActiveSupport::TestCase\n  teardown do\n    Setting.clear_cache\n  end\nend\n```\n\n---\n\n## How to manage Settings in the admin interface?\n\nIf you want to create an admin interface to editing the Settings, you can try methods in following:\n\nconfig/routes.rb\n\n```rb\nnamespace :admin do\n  resource :settings\nend\n```\n\napp/controllers/admin/settings_controller.rb\n\n```rb\nmodule Admin\n  class SettingsController \u003c ApplicationController\n    def create\n      @errors = ActiveModel::Errors.new\n      setting_params.keys.each do |key|\n        next if setting_params[key].nil?\n\n        setting = Setting.new(var: key)\n        setting.value = setting_params[key].strip\n        unless setting.valid?\n          @errors.merge!(setting.errors)\n        end\n      end\n\n      if @errors.any?\n        render :new\n      end\n\n      setting_params.keys.each do |key|\n        Setting.send(\"#{key}=\", setting_params[key].strip) unless setting_params[key].nil?\n      end\n\n      redirect_to admin_settings_path, notice: \"Setting was successfully updated.\"\n    end\n\n    private\n      def setting_params\n        params.require(:setting).permit(:host, :user_limits, :admin_emails,\n          :captcha_enable, :notification_options)\n      end\n  end\nend\n```\n\napp/views/admin/settings/show.html.erb\n\n```erb\n\u003c%= form_for(Setting.new, url: admin_settings_path) do |f| %\u003e\n  \u003c% if @errors.any? %\u003e\n    \u003cdiv class=\"alert alert-block alert-danger\"\u003e\n      \u003cul\u003e\n        \u003c% @errors.full_messages.each do |msg| %\u003e\n        \u003cli\u003e\u003c%= msg %\u003e\u003c/li\u003e\n        \u003c% end %\u003e\n      \u003c/ul\u003e\n    \u003c/div\u003e\n  \u003c% end %\u003e\n\n  \u003cdiv class=\"form-group\"\u003e\n    \u003clabel class=\"control-label\"\u003eHost\u003c/label\u003e\n    \u003c%= f.text_field :host, value: Setting.host, class: \"form-control\", placeholder: \"http://localhost\"  %\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"form-group form-checkbox\"\u003e\n    \u003clabel\u003e\n      \u003c%= f.check_box :captcha_enable, checked: Setting.captcha_enable? %\u003e\n      Enable/Disable Captcha\n    \u003c/label\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"form-group\"\u003e\n    \u003clabel class=\"control-label\"\u003eAdmin Emails\u003c/label\u003e\n    \u003c%= f.text_area :admin_emails, value: Setting.admin_emails.join(\"\\n\"), class: \"form-control\" %\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"form-group\"\u003e\n    \u003clabel class=\"control-label\"\u003eNotification options\u003c/label\u003e\n    \u003c%= f.text_area :notification_options, value: YAML.dump(Setting.notification_options), class: \"form-control\", style: \"height: 180px;\"  %\u003e\n    \u003cdiv class=\"form-text\"\u003e\n      Use YAML format to config the SMTP_html\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cdiv\u003e\n    \u003c%= f.submit 'Update Settings' %\u003e\n  \u003c/div\u003e\n\u003c% end %\u003e\n```\n\n## Special Cache Storage\n\nYou can use `cache_store` to change cache storage, default is `Rails.cache`.\n\nAdd `config/initializers/rails_settings.rb`\n\n```rb\nRailsSettings.configure do\n  self.cache_storage = ActiveSupport::Cache::RedisCacheStore.new(url: \"redis://localhost:6379\")\nend\n```\n\n## Scoped Settings\n\n\u003e 🚨 BREAK CHANGES WARNING:\n\u003e rails-settings-cached 2.x has redesigned the API, the new version will compatible with the stored setting values by an older version.\n\u003e When you want to upgrade 2.x, you must read the README again, and follow guides to change your Setting model.\n\u003e 0.x stable branch: https://github.com/huacnlee/rails-settings-cached/tree/0.x\n\n- [Backward compatible to support 0.x scoped settings](docs/backward-compatible-to-scoped-settings.md)\n\nFor new project / new user of rails-settings-cached. The [ActiveRecord::AttributeMethods::Serialization](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize) is best choice.\n\n\u003e This is reason of why rails-settings-cached 2.x removed **Scoped Settings** feature.\n\nFor example:\n\nWe wants a preferences setting for user.\n\n```rb\nclass User \u003c ActiveRecord::Base\n  serialize :preferences\nend\n\n@user = User.new\n@user.preferences[:receive_emails] = true\n@user.preferences[:public_email] = true\n@user.save\n```\n\n## Use cases:\n\n- [ruby-china/homeland](https://github.com/ruby-china/homeland) - master\n- [forem/forem](https://github.com/forem/forem) - 2.x\n- [siwapp/siwapp](https://github.com/siwapp/siwapp) - 2.x\n- [aidewoode/black_candy](https://github.com/aidewoode/black_candy) - 2.x\n- [huacnlee/bluedoc](https://github.com/huacnlee/bluedoc) - 2.x\n- [getzealot/zealot](https://github.com/getzealot/zealot) - 2.x\n- [kaishuu0123/rebacklogs](https://github.com/kaishuu0123/rebacklogs) - 2.x\n- [texterify/texterify](https://github.com/texterify/texterify) - 2.x\n- [mastodon/mastodon](https://github.com/mastodon/mastodon) - 0.6.x\n- [helpyio/helpy](https://github.com/helpyio/helpy) - 0.5.x\n- [maybe-finance/maybe](https://github.com/maybe-finance/maybe) - 2.x\n\nAnd more than [1K repositories](https://github.com/huacnlee/rails-settings-cached/network/dependents) used.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuacnlee%2Frails-settings-cached","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhuacnlee%2Frails-settings-cached","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuacnlee%2Frails-settings-cached/lists"}