thecore_settings 1.1.15 → 2.0.5

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +20 -0
  3. data/.devcontainer/devcontainer.json +33 -0
  4. data/.github/workflows/gempush.yml +34 -0
  5. data/.gitignore +542 -0
  6. data/.rakeTasks +7 -0
  7. data/.rspec +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +31 -0
  10. data/CHANGELOG.md +81 -0
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +275 -0
  13. data/LICENSE.txt +22 -0
  14. data/README.md +169 -0
  15. data/README.rdoc +68 -0
  16. data/Rakefile +6 -34
  17. data/app/{assets/config/thecore_settings_manifest.js → models/.keep} +0 -0
  18. data/app/models/thecore_settings/setting.rb +77 -0
  19. data/app/views/.keep +0 -0
  20. data/app/views/rails_admin/main/_setting_value.html.haml +41 -0
  21. data/bin/rails +13 -0
  22. data/config/locales/en.yml +29 -0
  23. data/config/locales/{thecore_settings.it.yml → it.yml} +2 -2
  24. data/config/locales/pt-BR.yml +28 -0
  25. data/config/locales/ru.yml +29 -0
  26. data/db/migrate/20161227101954_create_rails_admin_settings.rb +4 -4
  27. data/gemfiles/mongoid-6.0.gemfile +5 -0
  28. data/gemfiles/mongoid-6.3.gemfile +5 -0
  29. data/lib/generators/thecore_settings/migration_generator.rb +15 -0
  30. data/lib/generators/thecore_settings/templates/db/migrate/20161227101954_create_thecore_settings.rb +29 -0
  31. data/lib/generators/thecore_settings/templates/migration.rb +29 -0
  32. data/lib/thecore_settings/determine_mime_type.rb +193 -0
  33. data/lib/thecore_settings/dumper.rb +12 -0
  34. data/lib/thecore_settings/engine.rb +17 -3
  35. data/lib/thecore_settings/fallback.rb +21 -0
  36. data/lib/thecore_settings/hex_color_validator.rb +11 -0
  37. data/lib/thecore_settings/kinds.rb +34 -0
  38. data/lib/thecore_settings/mongoid.rb +19 -0
  39. data/lib/thecore_settings/namespaced.rb +210 -0
  40. data/lib/thecore_settings/processing.rb +217 -0
  41. data/lib/thecore_settings/rails_admin_config.rb +71 -0
  42. data/lib/thecore_settings/require_helpers.rb +86 -0
  43. data/lib/thecore_settings/settings.rb +95 -0
  44. data/lib/thecore_settings/storage/carrier_wave_uploader.rb +9 -0
  45. data/lib/thecore_settings/storage/shrine_uploader.rb +14 -0
  46. data/lib/thecore_settings/tasks.rb +35 -0
  47. data/lib/thecore_settings/uploads.rb +57 -0
  48. data/lib/thecore_settings/validation.rb +126 -0
  49. data/lib/thecore_settings/version.rb +1 -1
  50. data/lib/thecore_settings.rb +115 -123
  51. data/spec/advanced_usage_spec.rb +11 -0
  52. data/spec/carrierwave_spec.rb +41 -0
  53. data/spec/database_trickery_spec.rb +48 -0
  54. data/spec/defaults_spec.rb +87 -0
  55. data/spec/enabling_spec.rb +29 -0
  56. data/spec/factories/setting.rb +8 -0
  57. data/spec/label_spec.rb +16 -0
  58. data/spec/migration_spec.rb +20 -0
  59. data/spec/model_spec.rb +105 -0
  60. data/spec/namespaced_spec.rb +67 -0
  61. data/spec/paperclip_spec.rb +38 -0
  62. data/spec/settings_spec.rb +75 -0
  63. data/spec/shrine_spec.rb +34 -0
  64. data/spec/spec_helper.rb +85 -0
  65. data/spec/support/1024x768.gif +0 -0
  66. data/spec/support/database_cleaner.rb +10 -0
  67. data/spec/support/defaults.yml +23 -0
  68. data/spec/support/defaults_w_file.yml +19 -0
  69. data/spec/support/mongoid.rb +6 -0
  70. data/spec/support/mongoid.yml +6 -0
  71. data/spec/types_spec.rb +101 -0
  72. data/thecore_settings.gemspec +44 -0
  73. metadata +326 -63
  74. data/app/assets/stylesheets/rich/editor.css +0 -20
  75. data/config/initializers/rails_admin_requirements.rb +0 -22
  76. data/config/initializers/thecore_settings_abilities.rb +0 -22
  77. data/config/initializers/thecore_settings_post_init.rb +0 -9
  78. data/config/routes.rb +0 -3
  79. data/db/migrate/20161227101956_add_app_name.rb +0 -5
  80. data/lib/tasks/thecore_settings_tasks.rake +0 -4
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Documentation can be found on https://shrinerb.com/docs/plugins/determine_mime_type
6
+ module DetermineMimeType
7
+ LOG_SUBSCRIBER = -> (event) do
8
+ Shrine.logger.info "MIME Type (#{event.duration}ms) – #{{
9
+ io: event[:io].class,
10
+ uploader: event[:uploader],
11
+ }.inspect}"
12
+ end
13
+
14
+ def self.configure(uploader, log_subscriber: LOG_SUBSCRIBER, **opts)
15
+ uploader.opts[:determine_mime_type] ||= { analyzer: :file, analyzer_options: {} }
16
+ uploader.opts[:determine_mime_type].merge!(opts)
17
+
18
+ # instrumentation plugin integration
19
+ uploader.subscribe(:mime_type, &log_subscriber) if uploader.respond_to?(:subscribe)
20
+ end
21
+
22
+ module ClassMethods
23
+ # Determines the MIME type of the IO object by calling the specified
24
+ # analyzer.
25
+ def determine_mime_type(io)
26
+ analyzer = opts[:determine_mime_type][:analyzer]
27
+
28
+ analyzer = mime_type_analyzer(analyzer) if analyzer.is_a?(Symbol)
29
+ args = if analyzer.is_a?(Proc)
30
+ [io, mime_type_analyzers].take(analyzer.arity.abs)
31
+ else
32
+ [io, opts[:determine_mime_type][:analyzer_options]]
33
+ end
34
+
35
+ mime_type = instrument_mime_type(io) { analyzer.call(*args) }
36
+ io.rewind
37
+
38
+ mime_type
39
+ end
40
+ alias mime_type determine_mime_type
41
+
42
+ # Returns a hash of built-in MIME type analyzers, where keys are
43
+ # analyzer names and values are `#call`-able objects which accepts the
44
+ # IO object.
45
+ def mime_type_analyzers
46
+ @mime_type_analyzers ||= MimeTypeAnalyzer::SUPPORTED_TOOLS.inject({}) do |hash, tool|
47
+ hash.merge!(tool => mime_type_analyzer(tool))
48
+ end
49
+ end
50
+
51
+ # Returns callable mime type analyzer object.
52
+ def mime_type_analyzer(name)
53
+ MimeTypeAnalyzer.new(name)
54
+ end
55
+
56
+ private
57
+
58
+ # Sends a `mime_type.shrine` event for instrumentation plugin.
59
+ def instrument_mime_type(io, &block)
60
+ return yield unless respond_to?(:instrument)
61
+
62
+ instrument(:mime_type, io: io, &block)
63
+ end
64
+ end
65
+
66
+ module InstanceMethods
67
+ private
68
+
69
+ # Calls the configured MIME type analyzer.
70
+ def extract_mime_type(io)
71
+ self.class.determine_mime_type(io)
72
+ end
73
+ end
74
+
75
+ class MimeTypeAnalyzer
76
+ SUPPORTED_TOOLS = [:fastimage, :file, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime, :content_type]
77
+ MAGIC_NUMBER = 256 * 1024
78
+
79
+ def initialize(tool)
80
+ raise Error, "unknown mime type analyzer #{tool.inspect}, supported analyzers are: #{SUPPORTED_TOOLS.join(",")}" unless SUPPORTED_TOOLS.include?(tool)
81
+
82
+ @tool = tool
83
+ end
84
+
85
+ def call(io, options = {})
86
+ mime_type = send(:"extract_with_#{@tool}", io, options)
87
+ io.rewind
88
+
89
+ mime_type
90
+ end
91
+
92
+ private
93
+
94
+ def extract_with_file(io, options)
95
+ require "open3"
96
+
97
+ return nil if io.eof? # file command returns "application/x-empty" for empty files
98
+
99
+ Open3.popen3(*%W[file --mime-type --brief -]) do |stdin, stdout, stderr, thread|
100
+ begin
101
+ IO.copy_stream(io, stdin.binmode)
102
+ rescue Errno::EPIPE
103
+ end
104
+ stdin.close
105
+
106
+ status = thread.value
107
+
108
+ raise Error, "file command failed to spawn: #{stderr.read}" if status.nil?
109
+ raise Error, "file command failed: #{stderr.read}" unless status.success?
110
+
111
+ $stderr.print(stderr.read)
112
+
113
+ output = stdout.read.strip
114
+
115
+ raise Error, "file command failed: #{output}" if output.include?("cannot open")
116
+
117
+ output
118
+ end
119
+ rescue Errno::ENOENT
120
+ raise Error, "file command-line tool is not installed"
121
+ end
122
+
123
+ def extract_with_fastimage(io, options)
124
+ require "fastimage"
125
+
126
+ type = FastImage.type(io)
127
+ "image/#{type}" if type
128
+ end
129
+
130
+ def extract_with_filemagic(io, options)
131
+ require "filemagic"
132
+
133
+ return nil if io.eof? # FileMagic returns "application/x-empty" for empty files
134
+
135
+ FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
136
+ filemagic.buffer(io.read(MAGIC_NUMBER))
137
+ end
138
+ end
139
+
140
+ def extract_with_mimemagic(io, options)
141
+ require "mimemagic"
142
+
143
+ mime = MimeMagic.by_magic(io)
144
+ mime&.type
145
+ end
146
+
147
+ def extract_with_marcel(io, options)
148
+ require "marcel"
149
+
150
+ return nil if io.eof? # marcel returns "application/octet-stream" for empty files
151
+
152
+ filename = (options[:filename_fallback] ? extract_filename(io) : nil)
153
+ Marcel::MimeType.for(io, name: filename)
154
+ end
155
+
156
+ def extract_with_mime_types(io, options)
157
+ require "mime/types"
158
+
159
+ if filename = extract_filename(io)
160
+ mime_type = MIME::Types.of(filename).first
161
+ mime_type&.content_type
162
+ end
163
+ end
164
+
165
+ def extract_with_mini_mime(io, options)
166
+ require "mini_mime"
167
+
168
+ if filename = extract_filename(io)
169
+ info = MiniMime.lookup_by_filename(filename)
170
+ info&.content_type
171
+ end
172
+ end
173
+
174
+ def extract_with_content_type(io, options)
175
+ if io.respond_to?(:content_type) && io.content_type
176
+ io.content_type.split(";").first
177
+ end
178
+ end
179
+
180
+ def extract_filename(io)
181
+ if io.respond_to?(:original_filename)
182
+ io.original_filename
183
+ elsif io.respond_to?(:path)
184
+ File.basename(io.path)
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ register_plugin(:determine_mime_type, DetermineMimeType)
192
+ end
193
+ end
@@ -0,0 +1,12 @@
1
+ module ThecoreSettings
2
+ module Dumper
3
+ def self.dump(path)
4
+ ns = {}
5
+ ThecoreSettings::Setting.each do |s|
6
+ ns[s.ns] = {} if ns[s.ns].nil?
7
+ ns[s.ns][s.key] = s.as_yaml
8
+ end
9
+ File.write(path, ns.to_yaml)
10
+ end
11
+ end
12
+ end
@@ -1,10 +1,24 @@
1
1
  module ThecoreSettings
2
2
  class Engine < ::Rails::Engine
3
- initializer "thecore_settings.add_to_migrations" do |app|
3
+ rake_tasks do
4
+ require File.expand_path('../tasks', __FILE__)
5
+ end
6
+
7
+ initializer 'ThecoreSettings.install_after_action' do |app|
8
+ require File.dirname(__FILE__) + '/../../app/models/thecore_settings/setting.rb'
9
+
10
+ if defined?(ActionController) and defined?(ActionController::Base)
11
+ ActionController::Base.class_eval do
12
+ after_action { Settings.unload! }
13
+ end
14
+ end
15
+ end
16
+
17
+ initializer 'ThecoreSettings.add_to_migrations' do |app|
4
18
  unless app.root.to_s.match root.to_s
5
19
  # APPEND TO MAIN APP MIGRATIONS FROM THIS GEM
6
- config.paths["db/migrate"].expanded.each do |expanded_path|
7
- app.config.paths["db/migrate"] << expanded_path
20
+ config.paths['db/migrate'].expanded.each do |expanded_path|
21
+ app.config.paths['db/migrate'] << expanded_path
8
22
  end
9
23
  end
10
24
  end
@@ -0,0 +1,21 @@
1
+ module ThecoreSettings
2
+ # we are inheriting from BasicObject so we don't get a bunch of methods from
3
+ # Kernel or Object
4
+ class Fallback < BasicObject
5
+ def initialize(ns, fb)
6
+ @ns = ns
7
+ @fb = fb
8
+ end
9
+
10
+ def inspect
11
+ "#<ThecoreSettings::Fallback ns: #{@ns.inspect}, fb: #{@fb.inspect}>"
12
+ end
13
+
14
+ def method_missing(*args)
15
+ @ns.ns_mutex.synchronize do
16
+ @ns.fallback = @fb
17
+ @ns.__send__(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module ThecoreSettings
2
+ class HexColorValidator < ActiveModel::EachValidator
3
+ def validate_each(record, attribute, value)
4
+ record.errors[attribute] << (options[:message] || I18n.t('admin.settings.color_invalid')) unless value.blank? || self.class.matches?(value)
5
+ end
6
+ def self.matches?(value)
7
+ return false unless value
8
+ /^(?:[0-9a-f]{3})(?:[0-9a-f]{3})?$/i.match(value).nil? ? false : true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ module ThecoreSettings
2
+ def self.kinds
3
+ [
4
+ 'string',
5
+ 'text',
6
+ 'integer',
7
+ 'float',
8
+ 'boolean',
9
+ 'html',
10
+ 'code',
11
+ 'sanitized',
12
+ 'yaml',
13
+ 'phone',
14
+ 'phones',
15
+ 'email',
16
+ 'address',
17
+ 'file',
18
+ 'image',
19
+ 'url',
20
+ 'domain',
21
+ 'color',
22
+ 'strip_tags',
23
+ 'sanitize',
24
+ 'sanitize_code',
25
+ 'simple_format',
26
+ 'simple_format_raw',
27
+ 'json',
28
+ ]
29
+ end
30
+
31
+ def self.types
32
+ self.kinds
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module ThecoreSettings
2
+ module Mongoid
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ include ::Mongoid::Document
6
+ include ::Mongoid::Timestamps::Short
7
+
8
+ store_in collection: "thecore_settings"
9
+ field :enabled, type: ::Mongoid::VERSION.to_i < 4 ? Boolean : ::Mongoid::Boolean, default: true
10
+ field :kind, type: String, default: ThecoreSettings.types.first
11
+ field :ns, type: String, default: 'main'
12
+ field :key, type: String
13
+ field :raw, type: String
14
+ field :label, type: String
15
+ index({ns: 1, key: 1}, {unique: true, sparse: true})
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,210 @@
1
+ module ThecoreSettings
2
+ # we are inheriting from BasicObject so we don't get a bunch of methods from
3
+ # Kernel or Object
4
+ class Namespaced < BasicObject
5
+ attr_accessor :settings, :fallback
6
+ attr_reader :loaded, :mutex, :ns_mutex, :name
7
+
8
+ def initialize(name)
9
+ self.settings = {}
10
+ @mutex = ::Mutex.new
11
+ @ns_mutex = ::Mutex.new
12
+ @loaded = false
13
+ @locked = false
14
+ @name = name
15
+ end
16
+
17
+ def nil?
18
+ false
19
+ end
20
+ def inspect
21
+ "#<ThecoreSettings::Namespaced name: #{@name.inspect}, fallback: #{@fallback.inspect}, loaded: #{@loaded}>"
22
+ end
23
+ def pretty_inspect
24
+ inspect
25
+ end
26
+
27
+ def load!
28
+ mutex.synchronize do
29
+ return if loaded
30
+ @loaded = true
31
+ @settings = {}
32
+ ::ThecoreSettings::Setting.ns(@name).each do |setting|
33
+ @settings[setting.key] = setting
34
+ end
35
+ end
36
+ end
37
+
38
+ def unload!
39
+ mutex.synchronize do
40
+ @loaded = false
41
+ @settings = {}
42
+ end
43
+ end
44
+
45
+ # returns setting object
46
+ def get(key, options = {})
47
+ load!
48
+ key = key.to_s
49
+ mutex.synchronize do
50
+ @locked = true
51
+ v = @settings[key]
52
+ if v.nil?
53
+ unless @fallback.nil? || @fallback == @name
54
+ v = ::Settings.ns(@fallback).getnc(key)
55
+ end
56
+ if v.nil?
57
+ v = set(key, options[:default], options)
58
+ end
59
+ end
60
+ @locked = false
61
+ v
62
+ end
63
+ end
64
+
65
+ # returns setting object
66
+ def getnc(key)
67
+ load!
68
+ mutex.synchronize do
69
+ self.settings[key]
70
+ end
71
+ end
72
+
73
+ def set(key, value = nil, options = {})
74
+ load! unless @locked
75
+ key = key.to_s
76
+ options.symbolize_keys!
77
+
78
+ if !options[:type].nil? && options[:type] == 'yaml' && !value.nil?
79
+ if value.class.name != 'String'
80
+ value = value.to_yaml
81
+ end
82
+ end
83
+
84
+ options.merge!(value: value)
85
+ if @locked
86
+ write_to_database(key, options)
87
+ else
88
+ mutex.synchronize do
89
+ write_to_database(key, options)
90
+ end
91
+ end
92
+ end
93
+
94
+ def enabled?(key, options = {})
95
+ get(key, options).enabled?
96
+ end
97
+
98
+ def []=(key, value)
99
+ set(key, value)
100
+ end
101
+ def [](key)
102
+ get(key)
103
+ end
104
+
105
+ def destroy!(key)
106
+ load!
107
+ key = key.to_s
108
+ mutex.synchronize do
109
+ ::ThecoreSettings::Setting.where(ns: @name, key: key).destroy_all
110
+ @settings.delete(key)
111
+ end
112
+ end
113
+
114
+ def destroy_all!
115
+ mutex.synchronize do
116
+ ::ThecoreSettings::Setting.where(ns: @name).destroy_all
117
+ @loaded = false
118
+ @settings = {}
119
+ end
120
+ end
121
+
122
+ # returns processed setting value
123
+ def method_missing(key, *args)
124
+ key = key.to_s
125
+ if key.end_with?('_enabled?')
126
+ key = key[0..-10]
127
+ v = get(key)
128
+ if v.nil?
129
+ set(key, '').enabled
130
+ else
131
+ v.enabled
132
+ end
133
+ elsif key.end_with?('_enabled=')
134
+ key = key[0..-10]
135
+ v = get(key)
136
+ if ::ThecoreSettings.mongoid?
137
+ if ::Mongoid::VERSION >= "4.0.0"
138
+ v.set(enabled: args.first)
139
+ else
140
+ v.set("enabled", args.first)
141
+ end
142
+ else
143
+ v.enabled = args.first
144
+ v.save!
145
+ end
146
+ v.enabled
147
+ elsif key.end_with?('=')
148
+ key = key[0..-2]
149
+ options = args[1] || {}
150
+ value = args.first
151
+ set(key, value, options).val
152
+ else
153
+ v = get(key, args.first || {})
154
+ if v.nil?
155
+ ''
156
+ else
157
+ v.val
158
+ end
159
+ end
160
+ end
161
+
162
+ def write_to_database(key, options)
163
+ is_file = !options[:kind].nil? && (options[:kind] == 'image' || options[:kind] == 'file')
164
+ if is_file
165
+ options[:raw] = ''
166
+ file = options[:value]
167
+ else
168
+ options[:raw] = options[:value]
169
+ end
170
+
171
+ options.delete(:value)
172
+ options.delete(:default)
173
+ options[:ns] = @name
174
+
175
+ if @settings[key].nil?
176
+ options.delete(:overwrite)
177
+ v = ::ThecoreSettings::Setting.create(options.merge(key: key))
178
+ if !v.persisted?
179
+ if v.errors[:key].any?
180
+ v = ::ThecoreSettings::Setting.where(key: key).first
181
+ if v.nil?
182
+ ::Kernel.raise ::ThecoreSettings::PersistenceException, 'Fatal: error in key and not in DB'
183
+ end
184
+ else
185
+ ::Kernel.raise ::ThecoreSettings::PersistenceException, v.errors.full_messages.join(',')
186
+ end
187
+ end
188
+ @settings[key] = v
189
+ else
190
+ opts = options.dup
191
+ if options[:overwrite] == false && !@settings[key].value.blank?
192
+ opts.delete(:raw)
193
+ opts.delete(:value)
194
+ opts.delete(:enabled)
195
+ end
196
+ opts.delete(:overwrite)
197
+ # Pre Rails 6.1 vs Post Rails 6.1
198
+ # In Rails 6.1 update_attributes has been deprecated
199
+ @settings[key].respond_to?('update_attributes!') ? @settings[key].update_attributes!(opts) : @settings[key].update!(opts)
200
+ end
201
+ if is_file
202
+ if options[:overwrite] != false || !@settings[key].file?
203
+ @settings[key].file = file
204
+ @settings[key].save!
205
+ end
206
+ end
207
+ @settings[key]
208
+ end
209
+ end
210
+ end