rails-settings-cached 0.7.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "bundler/setup"
5
+ rescue LoadError
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ end
8
+
9
+ require "rdoc/task"
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "Rails Settings Cached"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
17
+ end
18
+
19
+ require "bundler/gem_tasks"
20
+
21
+ require "rake/testtask"
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << "test"
25
+ t.pattern = "test/**/*_test.rb"
26
+ t.verbose = false
27
+ t.warning = false
28
+ end
29
+
30
+ task default: :test
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/generators"
2
4
  require "rails/generators/migration"
3
5
 
4
- module Settings
6
+ module RailsSettings
5
7
  class InstallGenerator < Rails::Generators::NamedBase
8
+ namespace "settings:install"
6
9
  desc "Generate RailsSettings files."
7
10
  include Rails::Generators::Migration
8
11
 
9
12
  argument :name, type: :string, default: "setting"
10
13
 
11
- source_root File.expand_path("../templates", __FILE__)
14
+ source_root File.expand_path("templates", __dir__)
12
15
 
13
16
  @@migrations = false
14
17
 
@@ -27,16 +30,19 @@ module Settings
27
30
 
28
31
  def install_setting
29
32
  template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
30
- template "app.yml", File.join("config", "app.yml")
31
33
  migration_template "migration.rb", "db/migrate/create_settings.rb", migration_version: migration_version
32
34
  end
33
35
 
34
- def rails5?
35
- Rails.version.start_with? "5"
36
+ def rails_version_major
37
+ Rails::VERSION::MAJOR
38
+ end
39
+
40
+ def rails_version_minor
41
+ Rails::VERSION::MINOR
36
42
  end
37
43
 
38
44
  def migration_version
39
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if rails5?
45
+ "[#{rails_version_major}.#{rails_version_minor}]" if rails_version_major >= 5
40
46
  end
41
47
  end
42
48
  end
@@ -3,12 +3,10 @@ class CreateSettings < ActiveRecord::Migration<%= migration_version %>
3
3
  create_table :settings do |t|
4
4
  t.string :var, null: false
5
5
  t.text :value, null: true
6
- t.integer :thing_id, null: true
7
- t.string :thing_type, null: true, limit: 30
8
6
  t.timestamps
9
7
  end
10
8
 
11
- add_index :settings, %i(thing_type thing_id var), unique: true
9
+ add_index :settings, %i(var), unique: true
12
10
  end
13
11
 
14
12
  def self.down
@@ -1,7 +1,12 @@
1
1
  # RailsSettings Model
2
2
  class <%= class_name %> < RailsSettings::Base
3
- source Rails.root.join("config/app.yml")
3
+ cache_prefix { "v1" }
4
4
 
5
- # When config/app.yml has changed, you need change this prefix to v2, v3 ... to expires caches
6
- # cache_prefix { "v1" }
5
+ # Define your fields
6
+ # field :host, type: :string, default: "http://localhost:3000"
7
+ # field :default_locale, default: "en", type: :string
8
+ # field :confirmable_enable, default: "0", type: :boolean
9
+ # field :admin_emails, default: "admin@rubyonrails.org", type: :array
10
+ # field :omniauth_google_client_id, default: (ENV["OMNIAUTH_GOOGLE_CLIENT_ID"] || ""), type: :string, readonly: true
11
+ # field :omniauth_google_client_secret, default: (ENV["OMNIAUTH_GOOGLE_CLIENT_SECRET"] || ""), type: :string, readonly: true
7
12
  end
@@ -1,43 +1,204 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsSettings
2
- class Base < Settings
3
- def rewrite_cache
4
- Rails.cache.write(cache_key, value)
4
+ class ProcetedKeyError < RuntimeError
5
+ def initialize(key)
6
+ super("Can't use #{key} as setting key.")
5
7
  end
8
+ end
6
9
 
7
- def expire_cache
8
- Rails.cache.delete(cache_key)
10
+ class Base < ActiveRecord::Base
11
+ SEPARATOR_REGEXP = /[\n,;]+/
12
+ PROTECTED_KEYS = %w[var value]
13
+ self.table_name = table_name_prefix + "settings"
14
+
15
+ # get the value field, YAML decoded
16
+ def value
17
+ # rubocop:disable Security/YAMLLoad
18
+ YAML.load(self[:value]) if self[:value].present?
9
19
  end
10
20
 
11
- def cache_key
12
- self.class.cache_key(var, thing)
21
+ # set the value field, YAML encoded
22
+ def value=(new_value)
23
+ self[:value] = new_value.to_yaml
24
+ end
25
+
26
+ def clear_cache
27
+ self.class.clear_cache
13
28
  end
14
29
 
15
30
  class << self
31
+ def clear_cache
32
+ RequestCache.reset
33
+ Rails.cache.delete(cache_key)
34
+ end
35
+
36
+ def field(key, **opts)
37
+ _define_field(key, **opts)
38
+ end
39
+
40
+ def scope(name)
41
+ @scope = name.to_sym
42
+ yield
43
+ @scope = nil
44
+ end
45
+
46
+ def get_field(key)
47
+ @defined_fields.find { |field| field[:key] == key.to_s } || {}
48
+ end
49
+
16
50
  def cache_prefix(&block)
17
51
  @cache_prefix = block
18
52
  end
19
53
 
20
- def cache_key(var_name, scope_object)
21
- scope = ["rails_settings_cached"]
22
- scope << @cache_prefix.call if @cache_prefix
23
- scope << "#{scope_object.class.name}-#{scope_object.id}" if scope_object
24
- scope << var_name.to_s
25
- scope.join("/")
54
+ def cache_key
55
+ key_parts = ["rails-settings-cached"]
56
+ key_parts << @cache_prefix.call if @cache_prefix
57
+ key_parts.join("/")
26
58
  end
27
59
 
28
- def [](key)
29
- return super(key) unless rails_initialized?
30
- val = Rails.cache.fetch(cache_key(key, @object)) do
31
- super(key)
60
+ def keys
61
+ @defined_fields.map { |field| field[:key] }
62
+ end
63
+
64
+ def editable_keys
65
+ @defined_fields.reject { |field| field[:readonly] }.map { |field| field[:key] }
66
+ end
67
+
68
+ def readonly_keys
69
+ @defined_fields.select { |field| field[:readonly] }.map { |field| field[:key] }
70
+ end
71
+
72
+ attr_reader :defined_fields
73
+
74
+ private
75
+
76
+ def _define_field(key, default: nil, type: :string, readonly: false, separator: nil, validates: nil, **opts)
77
+ key = key.to_s
78
+
79
+ raise ProcetedKeyError.new(key) if PROTECTED_KEYS.include?(key)
80
+
81
+ @defined_fields ||= []
82
+ @defined_fields << {
83
+ scope: @scope,
84
+ key: key,
85
+ default: default,
86
+ type: type || :string,
87
+ readonly: readonly.nil? ? false : readonly,
88
+ options: opts
89
+ }
90
+
91
+ if readonly
92
+ define_singleton_method(key) do
93
+ result = default.is_a?(Proc) ? default.call : default
94
+ send(:_convert_string_to_typeof_value, type, result, separator: separator)
95
+ end
96
+ else
97
+ define_singleton_method(key) do
98
+ val = send(:_value_of, key)
99
+ result = nil
100
+ if !val.nil?
101
+ result = val
102
+ else
103
+ result = default
104
+ result = default.call if default.is_a?(Proc)
105
+ end
106
+
107
+ result = send(:_convert_string_to_typeof_value, type, result, separator: separator)
108
+
109
+ result
110
+ end
111
+
112
+ define_singleton_method("#{key}=") do |value|
113
+ var_name = key
114
+
115
+ record = find_by(var: var_name) || new(var: var_name)
116
+ value = send(:_convert_string_to_typeof_value, type, value, separator: separator)
117
+
118
+ record.value = value
119
+ record.save!
120
+
121
+ value
122
+ end
123
+
124
+ if validates
125
+ validates[:if] = proc { |item| item.var.to_s == key }
126
+ send(:validates, key, **validates)
127
+
128
+ define_method(:read_attribute_for_validation) do |_key|
129
+ self.value
130
+ end
131
+ end
132
+ end
133
+
134
+ if type == :boolean
135
+ define_singleton_method("#{key}?") do
136
+ send(key)
137
+ end
138
+ end
139
+
140
+ # delegate instance get method to class for support:
141
+ # setting = Setting.new
142
+ # setting.admin_emails
143
+ define_method(key) do
144
+ self.class.public_send(key)
145
+ end
146
+ end
147
+
148
+ def _convert_string_to_typeof_value(type, value, separator: nil)
149
+ return value unless [String, Integer, Float, BigDecimal].include?(value.class)
150
+
151
+ case type
152
+ when :boolean
153
+ ["true", "1", 1, true].include?(value)
154
+ when :array
155
+ value.split(separator || SEPARATOR_REGEXP).reject { |str| str.empty? }.map(&:strip)
156
+ when :hash
157
+ value = begin
158
+ YAML.safe_load(value).to_h
159
+ rescue
160
+ {}
161
+ end
162
+ value.deep_stringify_keys!
163
+ ActiveSupport::HashWithIndifferentAccess.new(value)
164
+ when :integer
165
+ value.to_i
166
+ when :float
167
+ value.to_f
168
+ when :big_decimal
169
+ value.to_d
170
+ else
171
+ value
172
+ end
173
+ end
174
+
175
+ def _value_of(var_name)
176
+ unless _table_exists?
177
+ # Fallback to default value if table was not ready (before migrate)
178
+ puts "WARNING: table: \"#{table_name}\" does not exist or not database connection, `#{name}.#{var_name}` fallback to returns the default value."
179
+ return nil
32
180
  end
33
- val
181
+
182
+ _all_settings[var_name]
183
+ end
184
+
185
+ def _table_exists?
186
+ table_exists?
187
+ rescue
188
+ false
34
189
  end
35
190
 
36
- # set a setting value by [] notation
37
- def []=(var_name, value)
38
- super
39
- Rails.cache.write(cache_key(var_name, @object), value)
40
- value
191
+ def rails_initialized?
192
+ Rails.application&.initialized?
193
+ end
194
+
195
+ def _all_settings
196
+ RequestCache.settings ||= Rails.cache.fetch(cache_key, expires_in: 1.week) do
197
+ vars = unscoped.select("var, value")
198
+ result = {}
199
+ vars.each { |record| result[record.var] = record.value }
200
+ result.with_indifferent_access
201
+ end
41
202
  end
42
203
  end
43
204
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsSettings
2
4
  class Railtie < Rails::Railtie
3
5
  initializer "rails_settings.active_record.initialization" do
4
- RailsSettings::Base.after_commit :rewrite_cache, on: %i(create update)
5
- RailsSettings::Base.after_commit :expire_cache, on: %i(destroy)
6
+ RailsSettings::Base.after_commit :clear_cache, on: %i[create update destroy]
6
7
  end
7
8
  end
8
9
  end
@@ -0,0 +1,30 @@
1
+ module RailsSettings
2
+ if defined? ActiveSupport::CurrentAttributes
3
+ # For storage all settings in Current, it will reset after per request completed.
4
+ # Base on ActiveSupport::CurrentAttributes
5
+ # https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
6
+ class RequestCache < ActiveSupport::CurrentAttributes
7
+ attribute :settings
8
+ end
9
+ else
10
+ # https://github.com/steveklabnik/request_store
11
+ # For Rails 5.0
12
+ require "request_store"
13
+
14
+ class RequestCache
15
+ class << self
16
+ def reset
17
+ self.settings = nil
18
+ end
19
+
20
+ def settings
21
+ RequestStore.store[:rails_settings_all_settings]
22
+ end
23
+
24
+ def settings=(val)
25
+ RequestStore.store[:rails_settings_all_settings]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsSettings
2
4
  class << self
3
5
  def version
4
- "0.7.2"
6
+ "2.7.0"
5
7
  end
6
8
  end
7
9
  end
@@ -1,8 +1,7 @@
1
- require_relative "rails-settings/settings"
1
+ # frozen_string_literal: true
2
+
2
3
  require_relative "rails-settings/base"
3
- require_relative "rails-settings/scoped_settings"
4
- require_relative "rails-settings/default"
5
- require_relative "rails-settings/extend"
4
+ require_relative "rails-settings/request_cache"
6
5
  require_relative "rails-settings/railtie"
7
6
  require_relative "rails-settings/version"
8
7
 
metadata CHANGED
@@ -1,17 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-settings-cached
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
- - Squeegy
9
- - Georg Ledermann
10
- - 100hz
11
8
  autorequire:
12
9
  bindir: bin
13
10
  cert_chain: []
14
- date: 2018-10-08 00:00:00.000000000 Z
11
+ date: 2021-07-08 00:00:00.000000000 Z
15
12
  dependencies:
16
13
  - !ruby/object:Gem::Dependency
17
14
  name: rails
@@ -19,16 +16,16 @@ dependencies:
19
16
  requirements:
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
- version: 4.2.0
19
+ version: 5.0.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: 4.2.0
26
+ version: 5.0.0
30
27
  - !ruby/object:Gem::Dependency
31
- name: sqlite3
28
+ name: codecov
32
29
  requirement: !ruby/object:Gem::Requirement
33
30
  requirements:
34
31
  - - ">="
@@ -42,21 +39,21 @@ dependencies:
42
39
  - !ruby/object:Gem::Version
43
40
  version: '0'
44
41
  - !ruby/object:Gem::Dependency
45
- name: rubocop
42
+ name: minitest
46
43
  requirement: !ruby/object:Gem::Requirement
47
44
  requirements:
48
- - - '='
45
+ - - ">="
49
46
  - !ruby/object:Gem::Version
50
- version: 0.46.0
47
+ version: '0'
51
48
  type: :development
52
49
  prerelease: false
53
50
  version_requirements: !ruby/object:Gem::Requirement
54
51
  requirements:
55
- - - '='
52
+ - - ">="
56
53
  - !ruby/object:Gem::Version
57
- version: 0.46.0
54
+ version: '0'
58
55
  - !ruby/object:Gem::Dependency
59
- name: simplecov
56
+ name: pg
60
57
  requirement: !ruby/object:Gem::Requirement
61
58
  requirements:
62
59
  - - ">="
@@ -70,7 +67,7 @@ dependencies:
70
67
  - !ruby/object:Gem::Version
71
68
  version: '0'
72
69
  - !ruby/object:Gem::Dependency
73
- name: rspec
70
+ name: rubocop
74
71
  requirement: !ruby/object:Gem::Requirement
75
72
  requirements:
76
73
  - - ">="
@@ -84,7 +81,21 @@ dependencies:
84
81
  - !ruby/object:Gem::Version
85
82
  version: '0'
86
83
  - !ruby/object:Gem::Dependency
87
- name: codecov
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
88
99
  requirement: !ruby/object:Gem::Requirement
89
100
  requirements:
90
101
  - - ">="
@@ -97,32 +108,31 @@ dependencies:
97
108
  - - ">="
98
109
  - !ruby/object:Gem::Version
99
110
  version: '0'
100
- description: "\n This is improved from rails-settings, added caching.\n Settings
101
- plugin for Rails that makes managing a table of global key,\n value pairs easy.
102
- Think of it like a global Hash stored in you database,\n that uses simple ActiveRecord
103
- like methods for manipulation.\n\n Keep track of any global setting that you dont
104
- want to hard code into your rails app.\n You can store any kind of object. Strings,
111
+ description: "\n The best solution for store global settings in Rails applications.\n\n
112
+ \ This gem will managing a table of а global key, value pairs easy. Think of it
113
+ like a\n global Hash stored in your database, that uses simple ActiveRecord like
114
+ methods for manipulation.\n\n Keep track of any global setting that you dont want
115
+ to hard code into your rails app.\n You can store any kind of object. Strings,
105
116
  numbers, arrays, or any object.\n "
106
117
  email: huacnlee@gmail.com
107
118
  executables: []
108
119
  extensions: []
109
120
  extra_rdoc_files: []
110
121
  files:
122
+ - MIT-LICENSE
111
123
  - README.md
124
+ - Rakefile
112
125
  - lib/generators/settings/install_generator.rb
113
- - lib/generators/settings/templates/app.yml
114
126
  - lib/generators/settings/templates/migration.rb
115
127
  - lib/generators/settings/templates/model.rb
116
128
  - lib/rails-settings-cached.rb
117
129
  - lib/rails-settings/base.rb
118
- - lib/rails-settings/default.rb
119
- - lib/rails-settings/extend.rb
120
130
  - lib/rails-settings/railtie.rb
121
- - lib/rails-settings/scoped_settings.rb
122
- - lib/rails-settings/settings.rb
131
+ - lib/rails-settings/request_cache.rb
123
132
  - lib/rails-settings/version.rb
124
133
  homepage: https://github.com/huacnlee/rails-settings-cached
125
- licenses: []
134
+ licenses:
135
+ - MIT
126
136
  metadata: {}
127
137
  post_install_message:
128
138
  rdoc_options: []
@@ -132,16 +142,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
142
  requirements:
133
143
  - - ">="
134
144
  - !ruby/object:Gem::Version
135
- version: '2.3'
145
+ version: '2.5'
136
146
  required_rubygems_version: !ruby/object:Gem::Requirement
137
147
  requirements:
138
148
  - - ">="
139
149
  - !ruby/object:Gem::Version
140
150
  version: '0'
141
151
  requirements: []
142
- rubyforge_project:
143
- rubygems_version: 2.7.6
152
+ rubygems_version: 3.2.3
144
153
  signing_key:
145
154
  specification_version: 4
146
- summary: Settings plugin for Rails that makes managing a table of global keys.
155
+ summary: The best solution for store global settings in Rails applications.
147
156
  test_files: []
@@ -1,13 +0,0 @@
1
- # config/app.yml for rails-settings-cached
2
- defaults: &defaults
3
- foo: "Foo"
4
- bar: 123
5
-
6
- development:
7
- <<: *defaults
8
-
9
- test:
10
- <<: *defaults
11
-
12
- production:
13
- <<: *defaults
@@ -1,40 +0,0 @@
1
- require "digest/md5"
2
-
3
- module RailsSettings
4
- class Default < ::Hash
5
- class MissingKey < StandardError; end
6
-
7
- class << self
8
- def enabled?
9
- source_path && File.exist?(source_path)
10
- end
11
-
12
- def source(value = nil)
13
- @source ||= value
14
- end
15
-
16
- def source_path
17
- @source || Rails.root.join("config/app.yml")
18
- end
19
-
20
- def [](key)
21
- # foo.bar.dar Nested fetch value
22
- return instance[key] if instance.key?(key)
23
- keys = key.to_s.split(".")
24
- instance.dig(*keys)
25
- end
26
-
27
- def instance
28
- return @instance if defined? @instance
29
- @instance = new
30
- end
31
- end
32
-
33
- def initialize
34
- content = open(self.class.source_path).read
35
- hash = content.empty? ? {} : YAML.load(ERB.new(content).result).to_hash
36
- hash = hash[Rails.env] || {}
37
- replace hash
38
- end
39
- end
40
- end
@@ -1,34 +0,0 @@
1
- module RailsSettings
2
- module Extend
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- scope :with_settings, lambda {
7
- joins("JOIN settings ON (settings.thing_id = #{table_name}.#{primary_key} AND
8
- settings.thing_type = '#{base_class.name}')")
9
- .select("DISTINCT #{table_name}.*")
10
- }
11
-
12
- scope :with_settings_for, lambda { |var|
13
- joins("JOIN settings ON (settings.thing_id = #{table_name}.#{primary_key} AND
14
- settings.thing_type = '#{base_class.name}') AND settings.var = '#{var}'")
15
- }
16
-
17
- scope :without_settings, lambda {
18
- joins("LEFT JOIN settings ON (settings.thing_id = #{table_name}.#{primary_key} AND
19
- settings.thing_type = '#{base_class.name}')")
20
- .where("settings.id IS NULL")
21
- }
22
-
23
- scope :without_settings_for, lambda { |var|
24
- where("settings.id IS NULL")
25
- .joins("LEFT JOIN settings ON (settings.thing_id = #{table_name}.#{primary_key} AND
26
- settings.thing_type = '#{base_class.name}') AND settings.var = '#{var}'")
27
- }
28
- end
29
-
30
- def settings
31
- ScopedSettings.for_thing(self)
32
- end
33
- end
34
- end
@@ -1,12 +0,0 @@
1
- module RailsSettings
2
- class ScopedSettings < Base
3
- def self.for_thing(object)
4
- @object = object
5
- self
6
- end
7
-
8
- def self.thing_scoped
9
- unscoped.where(thing_type: @object.class.base_class.to_s, thing_id: @object.id)
10
- end
11
- end
12
- end