Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/huacnlee/rails-settings-cached
Global settings for your Rails application.
https://github.com/huacnlee/rails-settings-cached
config configuration rails setting
Last synced: 15 days ago
JSON representation
Global settings for your Rails application.
- Host: GitHub
- URL: https://github.com/huacnlee/rails-settings-cached
- Owner: huacnlee
- License: other
- Created: 2011-01-20T08:41:50.000Z (almost 14 years ago)
- Default Branch: main
- Last Pushed: 2024-10-07T09:07:02.000Z (about 1 month ago)
- Last Synced: 2024-10-16T08:41:35.721Z (28 days ago)
- Topics: config, configuration, rails, setting
- Language: Ruby
- Homepage:
- Size: 400 KB
- Stars: 1,054
- Watchers: 11
- Forks: 202
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: MIT-LICENSE
Awesome Lists containing this project
README
# Rails Settings Cached
The best solution for store global settings in Rails applications.
This 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.
[![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)
## Installation
Edit your Gemfile:
```bash
$ bundle add rails-settings-cached
```Generate your settings:
```bash
$ rails g settings:install# Or use a custom name:
$ rails g settings:install AppConfig
```You will get `app/models/setting.rb`
```rb
class Setting < RailsSettings::Base
# cache_prefix { "v1" }scope :application do
field :app_name, default: "Rails Settings", validates: { presence: true, length: { in: 2..20 } }
field :host, default: "http://example.com", readonly: true
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 ..."
field :admin_emails, type: :array, default: %w[[email protected]]# lambda default value
field :welcome_message, type: :string, default: -> { "welcome to #{self.app_name}" }, validates: { length: { maximum: 255 } }
# Override array separator, default: /[\n,]/ split with \n or comma.
field :tips, type: :array, separator: /[\n]+/
endscope :limits do
field :user_limits, type: :integer, default: 20
field :exchange_rate, type: :float, default: 0.123
field :captcha_enable, type: :boolean, default: true
endfield :notification_options, type: :hash, default: {
send_all: true,
logging: true,
sender_email: "[email protected]"
}field :readonly_item, type: :integer, default: 100, readonly: true
end
```You must use the `field` method to statement the setting keys, otherwise you can't use it.
The `scope` method allows you to group the keys for admin UI.
Now just put that migration in the database with:
```bash
$ rails db:migrate
```## Usage
The syntax is easy. First, let's create some settings to keep track of:
```ruby
irb > Setting.host
"http://example.com"
irb > Setting.app_name
"Rails Settings"
irb > Setting.app_name = "Rails Settings Cached"
irb > Setting.app_name
"Rails Settings Cached"irb > Setting.user_limits
20
irb > Setting.user_limits = "30"
irb > Setting.user_limits
30
irb > Setting.user_limits = 45
irb > Setting.user_limits
45irb > Setting.captcha_enable
1
irb > Setting.captcha_enable?
true
irb > Setting.captcha_enable = "0"
irb > Setting.captcha_enable
false
irb > Setting.captcha_enable = "1"
irb > Setting.captcha_enable
true
irb > Setting.captcha_enable = "false"
irb > Setting.captcha_enable
false
irb > Setting.captcha_enable = "true"
irb > Setting.captcha_enable
true
irb > Setting.captcha_enable?
trueirb > Setting.admin_emails
["[email protected]"]
irb > Setting.admin_emails = %w[[email protected] [email protected]]
irb > Setting.admin_emails
["[email protected]", "[email protected]"]
irb > Setting.admin_emails = "[email protected],[email protected]\[email protected]"
irb > Setting.admin_emails
["[email protected]", "[email protected]", "[email protected]"]irb > Setting.notification_options
{
send_all: true,
logging: true,
sender_email: "[email protected]"
}
irb > Setting.notification_options = {
sender_email: "[email protected]"
}
irb > Setting.notification_options
{
sender_email: "[email protected]"
}
```### Get defined fields
> version 2.3+
```rb
# Get all keys
Setting.keys
=> ["app_name", "host", "default_locale", "readonly_item"]# Get editable keys
Setting.editable_keys
=> ["app_name", "default_locale"]# Get readonly keys
Setting.readonly_keys
=> ["host", "readonly_item"]# Get field
Setting.get_field("host")
=> { scope: :application, key: "host", type: :string, default: "http://example.com", readonly: true }
Setting.get_field("app_name")
=> { scope: :application, key: "app_name", type: :string, default: "Rails Settings", readonly: false }
Setting.get_field(:user_limits)
=> { scope: :limits, key: "user_limits", type: :integer, default: 20, readonly: false }
# Get field options
Setting.get_field("default_locale")[:options]
=> { option_values: %w[en zh-CN jp], help_text: "Bla bla ..." }
```### Custom type for setting
> Since: 2.9.0
You can write your custom field type by under `RailsSettings::Fields` module.
#### For example
```rb
module RailsSettings
module Fields
class YesNo < ::RailsSettings::Fields::Base
def serialize(value)
case value
when true then "YES"
when false then "NO"
else raise StandardError, 'invalid value'
end
enddef deserialize(value)
case value
when "YES" then true
when "NO" then false
else nil
end
end
end
end
end
```Now you can use `yes_no` type in you setting:
```rb
class Setting
field :custom_item, type: :yes_no, default: 'YES'
end
``````rb
irb> Setting.custom_item = 'YES'
irb> Setting.custom_item
true
irb> Setting.custom_item = 'NO'
irb> Setting.custom_item
false
```#### Get All defined fields
> version 2.7.0+
You can use `defined_fields` method to get all defined fields in Setting.
```rb
# Get editable fields and group by scope
editable_fields = Setting.defined_fields
.select { |field| !field[:readonly] }
.group_by { |field| field[:scope] }
```## Validations
You can use `validates` options to special the [Rails Validation](https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates) for fields.
```rb
class Setting < RailsSettings::Base
# cache_prefix { "v1" }
field :app_name, default: "Rails Settings", validates: { presence: true, length: { in: 2..20 } }
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]" } }
end
```Now validate will work on record save:
```rb
irb> Setting.app_name = ""
ActiveRecord::RecordInvalid: (Validation failed: App name can't be blank)
irb> Setting.app_name = "Rails Settings"
"Rails Settings"
irb> Setting.default_locale = "zh-TW"
ActiveRecord::RecordInvalid: (Validation failed: Default locale is not included in [zh-CN, en, jp])
irb> Setting.default_locale = "en"
"en"
```Validate by `save` / `valid?` method:
```rb
setting = Setting.find_or_initialize_by(var: :app_name)
setting.value = ""
setting.valid?
# => false
setting.errors.full_messages
# => ["App name can't be blank", "App name too short (minimum is 2 characters)"]setting = Setting.find_or_initialize_by(var: :default_locale)
setting.value = "zh-TW"
setting.save
# => false
setting.errors.full_messages
# => ["Default locale is not included in [zh-CN, en, jp]"]
setting.value = "en"
setting.valid?
# => true
```## Use Setting in Rails initializing:
In `version 2.3+` you can use Setting before Rails is initialized.
For example `config/initializers/devise.rb`
```rb
Devise.setup do |config|
if Setting.omniauth_google_client_id.present?
config.omniauth :google_oauth2, Setting.omniauth_google_client_id, Setting.omniauth_google_client_secret
end
end
``````rb
class Setting < RailsSettings::Base
field :omniauth_google_client_id, default: ENV["OMNIAUTH_GOOGLE_CLIENT_ID"]
field :omniauth_google_client_secret, default: ENV["OMNIAUTH_GOOGLE_CLIENT_SECRET"]
end
```## Readonly field
You may also want use Setting before Rails initialize:
```
config/environments/*.rb
```If you want do that do that, the setting field must has `readonly: true`.
For example:
```rb
class Setting < RailsSettings::Base
field :mailer_provider, default: (ENV["mailer_provider"] || "smtp"), readonly: true
field :mailer_options, type: :hash, readonly: true, default: {
address: ENV["mailer_options.address"],
port: ENV["mailer_options.port"],
domain: ENV["mailer_options.domain"],
user_name: ENV["mailer_options.user_name"],
password: ENV["mailer_options.password"],
authentication: ENV["mailer_options.authentication"] || "login",
enable_starttls_auto: ENV["mailer_options.enable_starttls_auto"]
}
end
```config/environments/production.rb
```rb
# You must require_relative directly in Rails 6.1+ in config/environments/production.rb
require_relative "../../app/models/setting"Rails.application.configure do
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = Setting.mailer_options.deep_symbolize_keys
end
```TIP: You also can follow this file to rewrite ActionMailer's `mail` method for configuration Mail options from Setting after Rails booted.
https://github.com/ruby-china/homeland/blob/main/app/mailers/application_mailer.rb#L19
## Caching flow:
```
Setting.host -> Check Cache -> Exist - Get value of key for cache -> Return
|
Fetch all key and values from DB -> Write Cache -> Get value of key for cache -> return
|
Return default value or nil
```In 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.
Each key update will expire the cache, so do not add some frequent update key.
## Change cache key
Some times you may need to force update cache, now you can use `cache_prefix`
```ruby
class Setting < RailsSettings::Base
cache_prefix { "you-prefix" }
...
end
```In testing, you need add `Setting.clear_cache` for each Test case:
```rb
class ActiveSupport::TestCase
teardown do
Setting.clear_cache
end
end
```---
## How to manage Settings in the admin interface?
If you want to create an admin interface to editing the Settings, you can try methods in following:
config/routes.rb
```rb
namespace :admin do
resource :settings
end
```app/controllers/admin/settings_controller.rb
```rb
module Admin
class SettingsController < ApplicationController
def create
@errors = ActiveModel::Errors.new
setting_params.keys.each do |key|
next if setting_params[key].nil?setting = Setting.new(var: key)
setting.value = setting_params[key].strip
unless setting.valid?
@errors.merge!(setting.errors)
end
endif @errors.any?
render :new
endsetting_params.keys.each do |key|
Setting.send("#{key}=", setting_params[key].strip) unless setting_params[key].nil?
endredirect_to admin_settings_path, notice: "Setting was successfully updated."
endprivate
def setting_params
params.require(:setting).permit(:host, :user_limits, :admin_emails,
:captcha_enable, :notification_options)
end
end
end
```app/views/admin/settings/show.html.erb
```erb
<%= form_for(Setting.new, url: admin_settings_path) do |f| %>
<% if @errors.any? %>
<% @errors.full_messages.each do |msg| %>
- <%= msg %>
<% end %>
<% end %>
Host
<%= f.text_field :host, value: Setting.host, class: "form-control", placeholder: "http://localhost" %>
<%= f.check_box :captcha_enable, checked: Setting.captcha_enable? %>
Enable/Disable Captcha
Admin Emails
<%= f.text_area :admin_emails, value: Setting.admin_emails.join("\n"), class: "form-control" %>
Notification options
<%= f.text_area :notification_options, value: YAML.dump(Setting.notification_options), class: "form-control", style: "height: 180px;" %>
Use YAML format to config the SMTP_html
<%= f.submit 'Update Settings' %>
<% end %>
```## Special Cache Storage
You can use `cache_store` to change cache storage, default is `Rails.cache`.
Add `config/initializers/rails_settings.rb`
```rb
RailsSettings.configure do
self.cache_storage = ActiveSupport::Cache::RedisCacheStore.new(url: "redis://localhost:6379")
end
```## Scoped Settings
> 🚨 BREAK CHANGES WARNING:
> rails-settings-cached 2.x has redesigned the API, the new version will compatible with the stored setting values by an older version.
> When you want to upgrade 2.x, you must read the README again, and follow guides to change your Setting model.
> 0.x stable branch: https://github.com/huacnlee/rails-settings-cached/tree/0.x- [Backward compatible to support 0.x scoped settings](docs/backward-compatible-to-scoped-settings.md)
For 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.
> This is reason of why rails-settings-cached 2.x removed **Scoped Settings** feature.
For example:
We wants a preferences setting for user.
```rb
class User < ActiveRecord::Base
serialize :preferences
end@user = User.new
@user.preferences[:receive_emails] = true
@user.preferences[:public_email] = true
@user.save
```## Use cases:
- [ruby-china/homeland](https://github.com/ruby-china/homeland) - master
- [forem/forem](https://github.com/forem/forem) - 2.x
- [siwapp/siwapp](https://github.com/siwapp/siwapp) - 2.x
- [aidewoode/black_candy](https://github.com/aidewoode/black_candy) - 2.x
- [huacnlee/bluedoc](https://github.com/huacnlee/bluedoc) - 2.x
- [getzealot/zealot](https://github.com/getzealot/zealot) - 2.x
- [kaishuu0123/rebacklogs](https://github.com/kaishuu0123/rebacklogs) - 2.x
- [texterify/texterify](https://github.com/texterify/texterify) - 2.x
- [mastodon/mastodon](https://github.com/mastodon/mastodon) - 0.6.x
- [helpyio/helpy](https://github.com/helpyio/helpy) - 0.5.x
- [maybe-finance/maybe](https://github.com/maybe-finance/maybe) - 2.xAnd more than [1K repositories](https://github.com/huacnlee/rails-settings-cached/network/dependents) used.