Created
May 5, 2015 19:21
-
-
Save donovan-duplessis/aba19427edeaf9794b55 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin | |
* Name: setting.rb | |
* Description: Generic Settings Model (Rails 4.1+) | |
* Version: 0.3 | |
* Author: Donovan du Plessis <donovan@binarytrooper.com> | |
* Date: 2014/07/31 | |
=end | |
change_log = """ | |
2014-07-23 DdP Initial version | |
2014-07-24 DdP - Add dump/reload/truncate maintenance tasks | |
- Add cache prefix (logical bucket) configuration | |
- Add documentation to setup, run and maintain | |
2014-07-31 DdP - Remove cache_prefix; use namespace on cache store config | |
- Add ask flag on reload method to allow bypass question | |
- Add account scope to reload, dump and truncate methods | |
- Add account name accessor and scope all queries with account | |
name, keeping cache keys unique by having account name | |
appended to namespace. | |
""" | |
=begin | |
Configuration | |
------------- | |
Generate Setting Model: | |
rails g model Setting key:string value:text account:string | |
Copy setting.rb into: | |
<rails-app>/app/models/ | |
Migrate Database: | |
rake db:migrate | |
Add gem to Gemfile: | |
gem 'highline' | |
bundle install | |
Usage | |
----- | |
Set Account (defaults to 'settings'): | |
Setting.account = 'development' | |
Create Settings (cached for cache_expire time period): | |
Setting.application = 'Test Application' | |
Setting.site = { name: 'Test', date: DateTime.now } | |
Retrieve Settings: | |
Setting.application | |
Setting.site | |
Setting.site[:name] | |
Rereive Settings (bypass cache, retrieve from database, re-cache) | |
Setting.application! | |
Setting.site! | |
Maintenance | |
----------- | |
Dump | |
Setting.dump [print settings from database] | |
Setting.dump(scoped: false) | |
Reload [reload settings from database and reinitialize cache] | |
Setting.reload | |
Truncate [remove all settings from database and clear cache] | |
Setting.truncate | |
Setting.truncate(scoped: false) | |
Setup Options | |
------------- | |
cache_expire: Set cache expiry time period, example: | |
- Setting.cache_expire = 300.seconds | 60.minutes | 6.hours | 1.year | |
account: Set account name to scope queries and add account name to cache | |
namespace to keep buckets unique per account, example: | |
- Setting.account = "account_name" | |
Cache Store Implementations (*optional) | |
--------------------------------------- | |
+ Redis | |
======= | |
Install Redis: | |
sudo apt-get install redis-server | |
Add gem to Gemfile: | |
gem 'redis-rails' | |
Setup cache store to use dalli: | |
[config/environments/[development|production].rb] | |
config.cache_store = :redis_store, { | |
host: 'localhost', | |
port: 6379, | |
db: 0, | |
namespace: Rails.application.class.parent_name.downcase | |
} | |
+ Memcache | |
========== | |
Install Memcached: | |
sudo apt-get install memcached | |
Add gem to Gemfile: | |
gem 'dalli' | |
Setup cache store to use dalli: | |
[config/environments/[development|production].rb] | |
config.cache_store = :dalli_store, %w( 127.0.0.1 ), | |
{ namespace: Rails.application.class.parent_name } | |
Cache Key Format: | |
----------------- | |
<namespace>:<account>:<keyname> = <value> | |
=end | |
require 'highline/import' | |
class Setting < ActiveRecord::Base | |
include Humanizeable | |
serialize :value | |
# Validations | |
validates :key, presence: true | |
validates :value, presence: true | |
validates_uniqueness_of :key, scope: [:account] | |
# Scopes | |
scope :scoped_to, ->(account) { where(account: account) } | |
scope :for_update, ->(account) { scoped_to(account).order(:key, :label) } | |
class << self | |
attr_accessor :cache_expire, :account | |
def cache_expire | |
@cache_expire ||= 1.month.to_i | |
end | |
def account | |
@account ||= name.downcase.pluralize | |
end | |
def method_missing(method, *args, &block) | |
query = method.to_s | |
case query.chars.last | |
when '=' | |
set(key: query.chop, value: args.first) | |
when '!' | |
get(key: query.chop, cache: false) | |
when /^\w+$/ | |
get(key: query) | |
else | |
nil | |
end | |
end | |
def dump(scoped: true) | |
_with_setting(scoped: scoped) do |setting| | |
puts "key=[#{setting.key}], value=[#{setting.value.to_s}]" | |
end | |
end | |
def dumpconfig(scoped: true) | |
values = [] | |
_with_setting(ask: false, scoped: scoped) do |setting| | |
values << "#{setting.key} = #{setting.value.to_s}" | |
end | |
return values | |
end | |
def reload(ask=true) | |
_with_setting(ask: ask) do |setting| | |
get(key: setting.key, cache: false) | |
end | |
end | |
def truncate(scoped: true) | |
_with_setting(scoped: scoped) do |setting| | |
setting.destroy unless setting.value.nil? | |
Rails.cache.delete(cachekey(setting.key)) | |
end | |
end | |
private | |
def _question(q) | |
ask "\033[1m\033[36m#{q}\033[0m \033[33m(y/n)\033[0m" | |
end | |
def _yesno?(prompt) | |
answer = _question(prompt) | |
case answer.downcase | |
when 'yes', 'y' | |
true | |
when 'no', 'n' | |
false | |
else | |
_yesno?(prompt) | |
end | |
end | |
def _with_setting(ask: true, scoped: true) | |
function = (caller[0][/`.*'/][1..-2]).titlecase | |
continue = ( | |
ask ? _yesno?("#{function} settings from database and cache?") : true) | |
if continue | |
(scoped ? scoped_to(account) : all).each do |setting| | |
yield(setting) | |
end | |
end | |
end | |
def cachekey(key) | |
[account, key].join(':') | |
end | |
def get(key:, cache: true) | |
if cache && (setting = Rails.cache.read(cachekey(key))) | |
setting | |
else | |
setting = scoped_to(account).where(key: key).first | |
if setting | |
Rails.cache.write( | |
cachekey(key), setting.value, expires_in: cache_expire) | |
setting.value | |
end | |
end | |
end | |
def set(key:, value:) | |
entry = scoped_to(account).where(key: key).first || new(key: key) | |
if value.nil? | |
entry.destroy unless entry.value.nil? | |
Rails.cache.delete(cachekey(key)) | |
else | |
entry.update_attributes(value: value, account: account) | |
Rails.cache.write(cachekey(key), value, expires_in: cache_expire) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment