ack_rails_admin_settings 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +17 -0
  7. data/CHANGELOG.md +53 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +192 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +133 -0
  12. data/Rakefile +9 -0
  13. data/app/models/rails_admin_settings/setting.rb +78 -0
  14. data/app/views/rails_admin/main/_setting_value.html.haml +104 -0
  15. data/config/locales/en.yml +28 -0
  16. data/config/locales/ru.yml +28 -0
  17. data/gemfiles/mongoid-3.1.gemfile +5 -0
  18. data/gemfiles/mongoid-4.0.gemfile +5 -0
  19. data/lib/ack_rails_admin_settings.rb +1 -0
  20. data/lib/generators/rails_admin_settings/migration_generator.rb +15 -0
  21. data/lib/generators/rails_admin_settings/templates/migration.rb +26 -0
  22. data/lib/rails_admin_settings/dumper.rb +12 -0
  23. data/lib/rails_admin_settings/engine.rb +17 -0
  24. data/lib/rails_admin_settings/fallback.rb +21 -0
  25. data/lib/rails_admin_settings/hex_color_validator.rb +11 -0
  26. data/lib/rails_admin_settings/kinds.rb +30 -0
  27. data/lib/rails_admin_settings/mongoid.rb +31 -0
  28. data/lib/rails_admin_settings/namespaced.rb +218 -0
  29. data/lib/rails_admin_settings/processing.rb +159 -0
  30. data/lib/rails_admin_settings/rails_admin_config.rb +74 -0
  31. data/lib/rails_admin_settings/require_helpers.rb +65 -0
  32. data/lib/rails_admin_settings/settings.rb +113 -0
  33. data/lib/rails_admin_settings/storage/carrierwave.rb +9 -0
  34. data/lib/rails_admin_settings/tasks.rb +35 -0
  35. data/lib/rails_admin_settings/uploads.rb +45 -0
  36. data/lib/rails_admin_settings/validation.rb +84 -0
  37. data/lib/rails_admin_settings/version.rb +3 -0
  38. data/lib/rails_admin_settings.rb +81 -0
  39. data/rails_admin_settings.gemspec +40 -0
  40. data/release.sh +6 -0
  41. data/spec/advanced_usage_spec.rb +11 -0
  42. data/spec/carrierwave_spec.rb +41 -0
  43. data/spec/database_trickery_spec.rb +48 -0
  44. data/spec/defaults_spec.rb +87 -0
  45. data/spec/enabling_spec.rb +29 -0
  46. data/spec/factories/setting.rb +8 -0
  47. data/spec/label_spec.rb +18 -0
  48. data/spec/migration_spec.rb +15 -0
  49. data/spec/model_spec.rb +45 -0
  50. data/spec/namespaced_spec.rb +67 -0
  51. data/spec/paperclip_spec.rb +39 -0
  52. data/spec/settings_spec.rb +75 -0
  53. data/spec/spec_helper.rb +42 -0
  54. data/spec/support/1024x768.gif +0 -0
  55. data/spec/support/database_cleaner.rb +10 -0
  56. data/spec/support/defaults.yml +23 -0
  57. data/spec/support/defaults_w_file.yml +19 -0
  58. data/spec/support/mongoid.rb +6 -0
  59. data/spec/support/mongoid.yml +6 -0
  60. data/spec/types_spec.rb +88 -0
  61. metadata +383 -0
@@ -0,0 +1,28 @@
1
+ ru:
2
+ admin:
3
+ settings:
4
+ label: 'Настройки'
5
+ no_ckeditor_detected: CKEditor не обнаружен — отображаю настройку как текстовое поле
6
+ phone_invalid: Неверный телефонный номер
7
+ phones_invalid: "Неверный телефонный номер: %{phones}"
8
+ email_invalid: Неверный email
9
+ yaml_invalid: Неверный синтаксис YAML
10
+ color_invalid: Неверный цвет
11
+ mongoid: &mongoid
12
+ models:
13
+ rails_admin_settings/setting: Настройки
14
+ attributes:
15
+ rails_admin_settings/setting:
16
+ c_at: Создано
17
+ u_at: Обновлено
18
+ enabled: Включено
19
+ ns: Группа
20
+ name: Название
21
+ key: Ключ
22
+ raw: Значение
23
+ type: Тип
24
+ kind: Тип
25
+ label: Подпись
26
+ activerecord:
27
+ <<: *mongoid
28
+
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongoid", github: "mongoid/mongoid", branch: "3.1.0-stable"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongoid", "~> 4.0.0.beta1"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1 @@
1
+ require "rails_admin_settings"
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ module RailsAdminSettings
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ desc 'RocketCMS migration generator'
10
+ def install
11
+ migration_template "migration.rb", "db/migrate/create_rails_admin_settings.rb"
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,26 @@
1
+ class CreateRailsAdminSettings < ActiveRecord::Migration
2
+ def change
3
+ create_table :rails_admin_settings do |t|
4
+ t.boolean :enabled, default: true
5
+ t.string :kind, null: false, default: 'string'
6
+ t.string :ns, default: 'main'
7
+ t.string :key, null: false
8
+ if Object.const_defined?('Geocoder')
9
+ t.float :latitude
10
+ t.float :longitude
11
+ end
12
+ t.text :raw
13
+ t.string :label
14
+ if defined?(Paperclip)
15
+ t.attachment :file
16
+ elsif defined?(CarrierWave)
17
+ t.string :file
18
+ end
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :rails_admin_settings, :key
23
+ add_index :rails_admin_settings, [:ns, :key], unique: true
24
+ end
25
+ end
26
+
@@ -0,0 +1,12 @@
1
+ module RailsAdminSettings
2
+ module Dumper
3
+ def self.dump(path)
4
+ ns = {}
5
+ RailsAdminSettings::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
@@ -0,0 +1,17 @@
1
+ module RailsAdminSettings
2
+ class Engine < ::Rails::Engine
3
+ rake_tasks do
4
+ require File.expand_path('../tasks', __FILE__)
5
+ end
6
+
7
+ initializer 'RailsAdminSettings Install after_filter' do |app|
8
+ require File.dirname(__FILE__) + '/../../app/models/rails_admin_settings/setting.rb'
9
+
10
+ if defined?(ActionController) and defined?(ActionController::Base)
11
+ ActionController::Base.class_eval do
12
+ after_filter { Settings.unload! }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module RailsAdminSettings
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
+ "#<RailsAdminSettings::Fallback ns: #{@ns.inspect}, fb: #{@fb.inspect}>".freeze
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 RailsAdminSettings
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,30 @@
1
+ module RailsAdminSettings
2
+ def self.kinds
3
+ [
4
+ 'string',
5
+ 'text',
6
+ 'integer',
7
+ 'boolean',
8
+ 'html',
9
+ 'code',
10
+ 'sanitized',
11
+ 'yaml',
12
+ 'phone',
13
+ 'phones',
14
+ 'email',
15
+ 'address',
16
+ 'file',
17
+ 'image',
18
+ 'url',
19
+ 'domain',
20
+ 'color',
21
+
22
+ 'js',
23
+ 'css'
24
+ ].freeze
25
+ end
26
+
27
+ def self.types
28
+ self.kinds
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module RailsAdminSettings
2
+ module Mongoid
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ include ::Mongoid::Document
6
+ include ::Mongoid::Timestamps::Short
7
+
8
+ store_in collection: "rails_admin_settings".freeze
9
+ field :enabled, type: ::Mongoid::VERSION.to_i < 4 ? Boolean : ::Mongoid::Boolean, default: true
10
+ field :kind, type: String, default: RailsAdminSettings.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
+
17
+ field :cache_keys_str, type: String, default: ""
18
+ def cache_keys
19
+ cache_keys_str.split(" ").map { |k| k.strip }.reject { |k| k.blank? }
20
+ end
21
+
22
+ after_save :clear_cache
23
+ def clear_cache
24
+ cache_keys.each do |k|
25
+ Rails.cache.delete(k)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,218 @@
1
+ module RailsAdminSettings
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
+ "#<RailsAdminSettings::Namespaced name: #{@name.inspect}, fallback: #{@fallback.inspect}, loaded: #{@loaded}>".freeze
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
+ ::RailsAdminSettings::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
+ unless options[:cache_keys].nil?
85
+ if options[:cache_keys].class.name == 'String'
86
+ options[:cache_keys_str] = options.delete(:cache_keys)
87
+ elsif options[:cache_keys].class.name == 'Array'
88
+ options[:cache_keys_str] = options.delete(:cache_keys).map { |k| k.to_s }.join(" ")
89
+ else
90
+ options[:cache_keys_str] = options.delete(:cache_keys).to_s
91
+ end
92
+ end
93
+
94
+ options.merge!(value: value)
95
+ if @locked
96
+ write_to_database(key, options)
97
+ else
98
+ mutex.synchronize do
99
+ write_to_database(key, options)
100
+ end
101
+ end
102
+ end
103
+
104
+ def enabled?(key, options = {})
105
+ get(key, options).enabled?
106
+ end
107
+
108
+ def []=(key, value)
109
+ set(key, value)
110
+ end
111
+ def [](key)
112
+ get(key)
113
+ end
114
+
115
+ def destroy!(key)
116
+ load!
117
+ key = key.to_s
118
+ mutex.synchronize do
119
+ ::RailsAdminSettings::Setting.where(ns: @name, key: key).destroy_all
120
+ @settings.delete(key)
121
+ end
122
+ end
123
+
124
+ def destroy_all!
125
+ mutex.synchronize do
126
+ ::RailsAdminSettings::Setting.where(ns: @name).destroy_all
127
+ @loaded = false
128
+ @settings = {}
129
+ end
130
+ end
131
+
132
+ # returns processed setting value
133
+ def method_missing(key, *args)
134
+ key = key.to_s
135
+ if key.end_with?('_enabled?')
136
+ key = key[0..-10]
137
+ v = get(key)
138
+ if v.nil?
139
+ set(key, '').enabled
140
+ else
141
+ v.enabled
142
+ end
143
+ elsif key.end_with?('_enabled=')
144
+ key = key[0..-10]
145
+ v = get(key)
146
+ if ::RailsAdminSettings.mongoid?
147
+ if ::Mongoid::VERSION >= "4.0.0"
148
+ v.set(enabled: args.first)
149
+ else
150
+ v.set("enabled", args.first)
151
+ end
152
+ else
153
+ v.enabled = args.first
154
+ v.save!
155
+ end
156
+ v.enabled
157
+ elsif key.end_with?('=')
158
+ key = key[0..-2]
159
+ options = args[1] || {}
160
+ value = args.first
161
+ set(key, value, options).val
162
+ else
163
+ v = get(key, args.first || {})
164
+ if v.nil?
165
+ ''
166
+ else
167
+ v.val
168
+ end
169
+ end
170
+ end
171
+
172
+ def write_to_database(key, options)
173
+ is_file = !options[:kind].nil? && (options[:kind] == 'image' || options[:kind] == 'file')
174
+ if is_file
175
+ options[:raw] = ''
176
+ file = options[:value]
177
+ else
178
+ options[:raw] = options[:value]
179
+ end
180
+
181
+ options.delete(:value)
182
+ options.delete(:default)
183
+ options[:ns] = @name
184
+
185
+ if @settings[key].nil?
186
+ options.delete(:overwrite)
187
+ v = ::RailsAdminSettings::Setting.create(options.merge(key: key))
188
+ if !v.persisted?
189
+ if v.errors[:key].any?
190
+ v = ::RailsAdminSettings::Setting.where(key: key).first
191
+ if v.nil?
192
+ ::Kernel.raise ::RailsAdminSettings::PersistenceException, 'Fatal: error in key and not in DB'
193
+ end
194
+ else
195
+ ::Kernel.raise ::RailsAdminSettings::PersistenceException, v.errors.full_messages.join(',')
196
+ end
197
+ end
198
+ @settings[key] = v
199
+ else
200
+ opts = options.dup
201
+ if options[:overwrite] == false && !@settings[key].value.blank?
202
+ opts.delete(:raw)
203
+ opts.delete(:value)
204
+ opts.delete(:enabled)
205
+ end
206
+ opts.delete(:overwrite)
207
+ @settings[key].update_attributes!(opts)
208
+ end
209
+ if is_file
210
+ if options[:overwrite] != false || !@settings[key].file?
211
+ @settings[key].file = file
212
+ @settings[key].save!
213
+ end
214
+ end
215
+ @settings[key]
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,159 @@
1
+ module RailsAdminSettings
2
+ module Processing
3
+ RailsAdminSettings.kinds.each do |dkind|
4
+ define_method "#{dkind}_kind?" do
5
+ dkind == kind
6
+ end
7
+ define_method "#{dkind}_type?" do
8
+ dkind == kind
9
+ end
10
+ end
11
+
12
+ def text_kind?
13
+ (RailsAdminSettings.kinds - ['phone', 'phones', 'integer', 'yaml', 'boolean']).include? kind
14
+ end
15
+
16
+ def upload_kind?
17
+ ['file', 'image'].include? kind
18
+ end
19
+
20
+ def html_kind?
21
+ ['html', 'code', 'sanitized'].include? kind
22
+ end
23
+ alias_method :text_type?, :text_kind?
24
+ alias_method :upload_type?, :upload_kind?
25
+ alias_method :html_type?, :html_kind?
26
+
27
+ def value
28
+ if upload_kind?
29
+ if file?
30
+ file.url
31
+ else
32
+ nil
33
+ end
34
+ elsif raw.blank? || disabled?
35
+ default_value
36
+ else
37
+ processed_value
38
+ end
39
+ end
40
+
41
+ def blank?
42
+ if file_kind?
43
+ file.url.nil?
44
+ elsif raw.blank? || disabled?
45
+ true
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ def to_s
52
+ if yaml_kind? || phone_kind? || integer_kind?
53
+ raw
54
+ else
55
+ value
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def sanitize_value
62
+ require_sanitize do
63
+ self.raw = Sanitize.clean(value, Sanitize::Config::RELAXED)
64
+ end
65
+ end
66
+
67
+ def default_value
68
+ if html_kind?
69
+ ''.html_safe
70
+ elsif text_kind?
71
+ ''
72
+ elsif integer_kind?
73
+ 0
74
+ elsif yaml_kind?
75
+ elsif boolean_kind?
76
+ false
77
+ elsif yaml_type?
78
+ nil
79
+ elsif phone_kind?
80
+ require_russian_phone do
81
+ RussianPhone::Number.new('')
82
+ end
83
+ elsif phones_kind?
84
+ []
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def default_serializable_value
91
+ if phones_kind?
92
+ ''
93
+ elsif boolean_type?
94
+ 'false'
95
+ else
96
+ default_value
97
+ end
98
+ end
99
+
100
+ def process_text
101
+ text = raw.dup
102
+ text.gsub!('{{year}}', Time.now.strftime('%Y'))
103
+ text.gsub! /\{\{year\|([\d]{4})\}\}/ do
104
+ if Time.now.strftime('%Y') == $1
105
+ $1
106
+ else
107
+ "#{$1}-#{Time.now.strftime('%Y')}"
108
+ end
109
+ end
110
+ text = text.html_safe if html_kind?
111
+ text
112
+ end
113
+
114
+ def load_phone
115
+ require_russian_phone do
116
+ RussianPhone::Number.new(raw)
117
+ end
118
+ end
119
+
120
+ def load_phones
121
+ require_russian_phone do
122
+ raw.gsub("\r", '').split("\n").map{|i| RussianPhone::Number.new(i)}
123
+ end
124
+ end
125
+
126
+ def load_yaml
127
+ require_safe_yaml do
128
+ YAML.safe_load(raw)
129
+ end
130
+ end
131
+
132
+ def processed_value
133
+ if text_kind?
134
+ process_text
135
+ elsif integer_kind?
136
+ raw.to_i
137
+ elsif yaml_kind?
138
+ load_yaml
139
+ elsif boolean_kind?
140
+ raw == 'true'
141
+ elsif phone_kind?
142
+ load_phone
143
+ elsif phones_kind?
144
+ load_phones
145
+ elsif file_kind?
146
+ file.url
147
+ else
148
+ puts "[rails_admin_settings] Unknown field kind: #{kind}".freeze
149
+ nil
150
+ end
151
+ end
152
+
153
+ def self.included(base)
154
+ base.class_eval do
155
+ alias_method :val, :value
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,74 @@
1
+ module RailsAdminSettings
2
+
3
+ module RailsAdminConfig
4
+
5
+ def self.included(base)
6
+ if base.respond_to?(:rails_admin)
7
+ base.rails_admin do
8
+ navigation_label I18n.t('admin.settings.label')
9
+
10
+ list do
11
+ if Object.const_defined?('RailsAdminToggleable')
12
+ field :enabled, :toggle
13
+ else
14
+ field :enabled
15
+ end
16
+ field :kind
17
+ field :ns
18
+ field :name
19
+ field :raw do
20
+ pretty_value do
21
+ if bindings[:object].file_kind?
22
+ "<a href='#{CGI::escapeHTML(bindings[:object].file.url)}'>#{CGI::escapeHTML(bindings[:object].to_path)}</a>".html_safe.freeze
23
+ elsif bindings[:object].image_kind?
24
+ "<a href='#{CGI::escapeHTML(bindings[:object].file.url)}'><img src='#{CGI::escapeHTML(bindings[:object].file.url)}' /></a>".html_safe.freeze
25
+ else
26
+ value
27
+ end
28
+ end
29
+ end
30
+ if ::Settings.table_exists?
31
+ nss = ::RailsAdminSettings::Setting.pluck(:ns).uniq.map { |c| "ns_#{c.gsub('-', '_')}".to_sym }
32
+ scopes([nil] + nss)
33
+ end
34
+ end
35
+
36
+ edit do
37
+ field :enabled
38
+ field :label do
39
+ read_only true
40
+ help false
41
+ end
42
+ field :kind do
43
+ read_only true
44
+ help false
45
+ end
46
+ field :raw do
47
+ partial "setting_value".freeze
48
+ visible do
49
+ !bindings[:object].upload_kind?
50
+ end
51
+ end
52
+ if Settings.file_uploads_supported
53
+ field :file, Settings.file_uploads_engine do
54
+ visible do
55
+ bindings[:object].upload_kind?
56
+ end
57
+ end
58
+ end
59
+ field :cache_keys_str, :text do
60
+ visible do
61
+ render_object = (bindings[:controller] || bindings[:view])
62
+ render_object and render_object.current_user.admin?
63
+ end
64
+ end
65
+ end
66
+ end
67
+ else
68
+ puts "[rails_admin_settings] Problem: model does not respond to rails_admin: this should not happen"
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end