rails-settings-cached 0.7.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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