system_settings 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -13
  3. data/app/assets/config/manifest.js +1 -0
  4. data/app/assets/stylesheets/system_settings/application.css +227 -0
  5. data/app/controllers/system_settings/application_controller.rb +2 -6
  6. data/app/controllers/system_settings/settings_controller.rb +21 -16
  7. data/app/helpers/system_settings/application_helper.rb +34 -0
  8. data/app/models/system_settings/application_record.rb +2 -0
  9. data/app/models/system_settings/boolean_setting.rb +2 -0
  10. data/app/models/system_settings/configurator.rb +38 -10
  11. data/app/models/system_settings/decimal_list_setting.rb +8 -0
  12. data/app/models/system_settings/decimal_setting.rb +8 -0
  13. data/app/models/system_settings/errors/error.rb +2 -0
  14. data/app/models/system_settings/errors/not_found_error.rb +3 -1
  15. data/app/models/system_settings/errors/not_loaded_error.rb +8 -0
  16. data/app/models/system_settings/errors/settings_read_error.rb +2 -0
  17. data/app/models/system_settings/integer_list_setting.rb +3 -1
  18. data/app/models/system_settings/integer_setting.rb +3 -1
  19. data/app/models/system_settings/list_of_decimals_validator.rb +37 -0
  20. data/app/models/system_settings/list_of_integers_validator.rb +2 -0
  21. data/app/models/system_settings/list_of_strings_validator.rb +2 -0
  22. data/app/models/system_settings/setting.rb +2 -0
  23. data/app/models/system_settings/string_list_setting.rb +2 -0
  24. data/app/models/system_settings/string_setting.rb +7 -0
  25. data/app/models/system_settings/type/decimal_list.rb +42 -0
  26. data/app/models/system_settings/type/integer_list.rb +10 -15
  27. data/app/models/system_settings/type/string_list.rb +2 -0
  28. data/app/views/layouts/system_settings/application.html.erb +21 -0
  29. data/app/views/system_settings/settings/_common_attributes.html.erb +14 -0
  30. data/app/views/system_settings/settings/_form.html.erb +18 -0
  31. data/app/views/system_settings/settings/edit.html.erb +2 -0
  32. data/app/views/system_settings/settings/index.html.erb +29 -0
  33. data/app/views/system_settings/settings/show.html.erb +9 -0
  34. data/config/locales/system_settings.en.yml +27 -1
  35. data/config/routes.rb +7 -4
  36. data/lib/system_settings.rb +13 -7
  37. data/lib/system_settings/engine.rb +6 -13
  38. data/lib/system_settings/version.rb +3 -1
  39. metadata +17 -14
  40. data/app/controllers/system_settings/root_controller.rb +0 -11
  41. data/app/models/system_settings/pagination.rb +0 -18
  42. data/frontend/build/asset-manifest.json +0 -11
  43. data/frontend/build/favicon.ico +0 -0
  44. data/frontend/build/index.html +0 -1
  45. data/frontend/build/precache-manifest.cace9a6c696c96461d63a2e3a1cca55e.js +0 -22
  46. data/frontend/build/service-worker.js +0 -39
  47. data/frontend/build/static/css/main.003e6dc8.chunk.css +0 -1
  48. data/frontend/build/static/js/2.42b63415.chunk.js +0 -1
  49. data/frontend/build/static/js/main.9315e265.chunk.js +0 -1
  50. data/frontend/build/static/js/runtime~main.a09e9b82.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2436379d110a7e6416bce537892e89d29c0bf9ae6319abc843766010f9ed5e03
4
- data.tar.gz: ee3c15960c0cde816a6078abd303fb892dddd28609990176ab427d35602e435d
3
+ metadata.gz: 321c73f1b71d88e67c98191025ac69b6fa58ea6dbbaa838cb5a6eaea6622337a
4
+ data.tar.gz: 17bd42a54230f37063f618fe2cfebc3875410d3879757e9ed38138990c6a952d
5
5
  SHA512:
6
- metadata.gz: dc1d77aade5cdebcd53fd487d91a1a24c1cc0b4b444ce5b9ebab3fe0439f89df68e320497453a220c7b4c1b8c648515c56a4a7defb73cd379f71c6b04c702182
7
- data.tar.gz: a85ef1e09a368464d980cda6a5ff180191d9bb5dda3bdb49a80701a1c5c80b72935a5e76b492a21a82654a730956a3618ee428ed1c03872ecc312dc1aebcd2f9
6
+ metadata.gz: 4231c986165f29d944aec79b4cc5239ccc9f7d614ef710ec77e452a238f8579ec3afcf20c09c0701ec49b3a89547ebbfcc3e4f395a71d024b1349fadb8df1cfc
7
+ data.tar.gz: bab2dd0fbf7b7f5f38ea38e4ebdcb1d732eb0254a2e3278925ac16408cca8d3c05c898137c92e7916c2b4f85c6105a9ad1e1ec5be0e51eb9d8a0fa617ff3fd47
data/README.md CHANGED
@@ -42,10 +42,14 @@ string :default_locale, value: "en"
42
42
  integer :default_records_per_page, value: 25
43
43
  integer :remainder_interval_in_hours, value: 48
44
44
 
45
- # Array type strings and integers
45
+ # Decimal values
46
+ decimal :max_temp, value: 95.2
47
+
48
+ # Array type strings, integers and decimals
46
49
  string_list :admin_emails, description: "Will receive alerts"
47
50
  string_list :upload_allowed_extensions, value: ["docx", "pdf", "txt"]
48
51
  integer_list :lucky_numbers, description: "Prime numbers are more effective", value: [2, 3, 5, 11]
52
+ decimal_list :allowed_multipliers, value: [12.3, 99, BigDecimal("-87")]
49
53
  ```
50
54
 
51
55
  Load values from `config/system_settings.rb` into database:
@@ -93,19 +97,16 @@ If you would like to store your settings somewhere else than `config/system_sett
93
97
 
94
98
  ## Using System Settings in tests
95
99
 
96
- Your test suite probably clears database before/after every test example. Fortunately is very easy to load fresh settings from configuration file. It can be done by running `SystemSettings.load`.
100
+ Your test suite probably clears database before/after every test example. Fortunately is very easy to load fresh settings from configuration file.
101
+ It can be done by running `SystemSettings.load`. It will persist all loaded settings. But if you would like to persist only a subset of loaded settings run `SystemSettings.load(:one, :two, :three)`.
102
+
97
103
  And if you modify settings values in test example you can reset to defaults with `SystemSettings.reset_to_defaults`.
98
104
 
99
105
 
100
106
  ## Development
101
107
 
102
- Required development dependencies:
103
- * [Node.js](https://nodejs.org/) - JavaScript runtime
104
- * [Yarn](https://yarnpkg.com/) - package manager
105
-
106
108
  Optional development tools:
107
- * [overmind](https://github.com/DarthSim/overmind) - Process manager for Procfile-based applications and tmux
108
- * [direnv](https://direnv.net/) - Unclutter your .profile
109
+ * [direnv](https://direnv.net/) - Unclutter your .profile
109
110
 
110
111
  Required environment variables:
111
112
  * `RAILS_VERSION`
@@ -120,16 +121,13 @@ Getting started with development:
120
121
  1) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 bundle`
121
122
  2) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails db:create db:migrate`
122
123
  3) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails test`
123
- 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails frontend:install`
124
- 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 ./bin/rails app:system_settings:load`
125
- 4) `RAILS_VERSION=5.2.3 SQLITE3_VERSION=1.4.1 overmind start`
126
-
127
124
 
128
125
  ## Build status
129
126
  System Settings is being tested with Rails versions - `5.0`, `5.1`, `5.2`, `6.0`, `rails repo master branch`
130
127
 
131
128
  [![Build Status](https://dev.azure.com/kristsozols/System%20Settings/_apis/build/status/krists.system_settings?branchName=master)](https://dev.azure.com/kristsozols/System%20Settings/_build/latest?definitionId=1&branchName=master)
132
-
129
+ [![Maintainability](https://api.codeclimate.com/v1/badges/3ad889ca36f62bad04dc/maintainability)](https://codeclimate.com/github/krists/system_settings/maintainability)
130
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/3ad889ca36f62bad04dc/test_coverage)](https://codeclimate.com/github/krists/system_settings/test_coverage)
133
131
 
134
132
  ## License
135
133
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets .css
@@ -0,0 +1,227 @@
1
+ html {
2
+ box-sizing: border-box;
3
+ font-size: 14px;
4
+ font-family: Arial, Helvetica, sans-serif;
5
+ overflow-y: scroll;
6
+ }
7
+
8
+ *, *:before, *:after {box-sizing: inherit;}
9
+
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-weight: normal;
14
+ min-height: 100vh;
15
+ position: relative;
16
+ display: flex;
17
+ flex-direction: column;
18
+ color: #233656;
19
+ background: white;
20
+ font-size: 1rem;
21
+ line-height: 1.5rem;
22
+ }
23
+
24
+ header {
25
+ border-bottom: 1px solid #e1e1e1;
26
+ height: 2rem;
27
+ padding: 0.5rem 0;
28
+ box-sizing: content-box;
29
+ background: white;
30
+ position: sticky;
31
+ top: 0;
32
+ width: 100%;
33
+ }
34
+
35
+ main {
36
+ padding: 3rem 0;
37
+ }
38
+
39
+ header .container,
40
+ main .container {
41
+ max-width: 1020px;
42
+ margin: 0 auto;
43
+ }
44
+
45
+ a { color: #233656; text-decoration: none; }
46
+ a:hover { text-decoration: underline; }
47
+
48
+ .app-name, .app-name:hover, .app-name:active {
49
+ font-size: 1.5rem;
50
+ line-height: 2rem;
51
+ font-weight: bold;
52
+ user-select: none;
53
+ text-decoration: none;
54
+ }
55
+
56
+ .sys-name {
57
+ display: inline-block;
58
+ background: #f2f2f2;
59
+ border-radius: 3px;
60
+ padding: 0 0.5em;
61
+ font-family: "Courier New", Courier, monospace;
62
+ font-size: 0.75em;
63
+ max-width: 100%;
64
+ }
65
+
66
+ table th {
67
+ text-align: left;
68
+ }
69
+
70
+ .settings-table {
71
+ width: 100%;
72
+ table-layout: fixed;
73
+ white-space: nowrap;
74
+ user-select: none;
75
+ }
76
+
77
+ .settings-table td:empty:after {
78
+ content: "-";
79
+ }
80
+
81
+ .settings-table td {
82
+ overflow: hidden;
83
+ text-overflow: ellipsis;
84
+ }
85
+
86
+ .settings-table .description { width: 35% }
87
+ .settings-table .value { width: 25% }
88
+
89
+ .settings-table tbody td.no-entries {
90
+ color: #737373;
91
+ padding-top: 1rem;
92
+ margin-bottom: 1rem;
93
+ }
94
+
95
+ .settings-table tfoot td {
96
+ font-size: 0.8em;
97
+ padding-top: 1rem;
98
+ margin-bottom: 1rem;
99
+ }
100
+
101
+ .settings-table .value .value-part, .attribute .value .value-part {
102
+ max-width: 100%;
103
+ margin-right: 0.25em;
104
+ }
105
+
106
+ .settings-table .value .value-part:last-child, .attribute .value .value-part:last-child {
107
+ margin-right: 0;
108
+ }
109
+
110
+ .attribute {
111
+ display: flex;
112
+ flex-direction: column;
113
+ margin-bottom: 1rem;
114
+ }
115
+
116
+ .attribute .name {
117
+ font-size: 0.8em;
118
+ color: #737373;
119
+ user-select: none;
120
+ }
121
+
122
+ .attribute .value {
123
+ align-self: flex-start;
124
+ }
125
+ .attribute .value:empty:after {
126
+ content: "-";
127
+ }
128
+
129
+ .attribute .field_with_errors .name, .attribute .errors {
130
+ color: #d01d1d;
131
+ }
132
+ .attribute .hint {
133
+ font-size: 0.8em;
134
+ display: block;
135
+ }
136
+
137
+ .buttons {
138
+ border-top: 1px dotted #f2f2f2;
139
+ padding-top: 1rem;
140
+ margin-bottom: 1rem;
141
+ }
142
+ .buttons > * {
143
+ margin-right: 0.5rem;
144
+ }
145
+
146
+ button, input[type=submit], input[type=button], a.button {
147
+ display: inline-block;
148
+ cursor: pointer;
149
+ border: none;
150
+ border-radius: 3px;
151
+ padding: 0.375rem 0.75rem;
152
+ font-size: 1rem;
153
+ line-height: 1.5;
154
+ user-select: none;
155
+ white-space: nowrap;
156
+ }
157
+
158
+ button, input[type=submit], input[type=button] {
159
+ color: #4d4d4d;
160
+ background-color: #d9d9d9;
161
+ transition: background-color 0.1s ease-in-out;
162
+ outline: none;
163
+ }
164
+
165
+ button:focus, button:hover, input[type=submit]:focus, input[type=submit]:hover, input[type=button]:focus, input[type=button]:hover {
166
+ background-color: #b8b8b8;
167
+ }
168
+
169
+ button:active, input[type=submit]:active, input[type=button]:active {
170
+ background-color: #828282;
171
+ }
172
+
173
+ a.button {
174
+ color: #4d4d4d;
175
+ background-color: transparent;
176
+ transition: background-color 0.1s ease-in-out;
177
+ outline: none;
178
+ text-decoration: none;
179
+ }
180
+
181
+ a.button:focus, a.button:hover {
182
+ background-color: rgba(0, 0, 0, 0.15);
183
+ }
184
+
185
+ a.button:active {
186
+ background-color: rgba(0, 0, 0, 0.4);
187
+ }
188
+
189
+ button.primary, input[type=submit].primary, input[type=button].primary, a.button.primary {
190
+ color: #4d4d4d;
191
+ background-color: #a6a6a6;
192
+ transition: background-color 0.1s ease-in-out;
193
+ outline: none;
194
+ }
195
+
196
+ button.primary:focus, button.primary:hover, input[type=submit].primary:focus, input[type=submit].primary:hover, input[type=button].primary:focus, input[type=button].primary:hover, a.button.primary:focus, a.button.primary:hover {
197
+ background-color: #8d8d8d;
198
+ }
199
+
200
+ button.primary:active, input[type=submit].primary:active, input[type=button].primary:active, a.button.primary:active {
201
+ background-color: #646464;
202
+ }
203
+
204
+ input[type=text] {
205
+ border: none;
206
+ border-radius: 3px;
207
+ padding: 0.5em;
208
+ font-size: 1rem;
209
+ line-height: normal;
210
+ color: #4d4d4d;
211
+ background-color: #e6e6e6;
212
+ transition: background-color 0.1s ease-in-out;
213
+ outline: none;
214
+ box-sizing: content-box;
215
+ width: auto;
216
+ min-width: 12ch;
217
+ max-width: calc(100% - 2em);
218
+ will-change: width;
219
+ }
220
+
221
+ input[type=text]:focus {
222
+ background-color: #c4c4c4;
223
+ }
224
+
225
+ input[type=text]:active {
226
+ background-color: #bfbfbf;
227
+ }
@@ -1,11 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SystemSettings
2
4
  class ApplicationController < ActionController::Base
3
5
  protect_from_forgery with: :exception
4
-
5
- private
6
-
7
- def add_authenticity_token
8
- response.headers["Authenticity-Token"] = form_authenticity_token if protect_against_forgery?
9
- end
10
6
  end
11
7
  end
@@ -1,29 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SystemSettings
2
4
  class SettingsController < SystemSettings::ApplicationController
3
- RETURN_ATTRIBUTES = ["id", "name", "type", "value", "description"].freeze
5
+ RETURN_ATTRIBUTES = %w[id name type value description].freeze
6
+ before_action :set_setting, only: [:edit, :show, :update]
4
7
 
5
8
  def index
6
- @total_count = SystemSettings::Setting.count
7
- @settings = SystemSettings::Setting.order(:name).extending(SystemSettings::Pagination).page(params[:page], per_page: params[:per])
8
- render json: {
9
- items: @settings.map { |s| s.as_json(only: RETURN_ATTRIBUTES) },
10
- total_count: @total_count
11
- }
9
+ @settings = SystemSettings::Setting.order(:name)
12
10
  end
13
11
 
14
- def show
15
- @setting = SystemSettings::Setting.find(params[:id])
16
- add_authenticity_token
17
- render json: @setting.as_json(only: RETURN_ATTRIBUTES)
18
- end
12
+ def edit; end
13
+
14
+ def show; end
19
15
 
20
16
  def update
21
- @setting = SystemSettings::Setting.find(params[:id])
22
- if @setting.update(value: params[:value])
23
- render json: @setting.as_json(only: RETURN_ATTRIBUTES)
17
+ if @setting.update(setting_params)
18
+ redirect_to setting_path(@setting)
24
19
  else
25
- render json: {errors: @setting.errors.as_json}, status: :unprocessable_entity
20
+ render :edit
26
21
  end
27
22
  end
23
+
24
+ private
25
+
26
+ def set_setting
27
+ @setting = SystemSettings::Setting.find(params[:id])
28
+ end
29
+
30
+ def setting_params
31
+ params.require(:setting).permit(:value)
32
+ end
28
33
  end
29
34
  end
@@ -0,0 +1,34 @@
1
+ module SystemSettings::ApplicationHelper
2
+ SEPARATOR = ";".freeze
3
+
4
+ def format_value(value)
5
+ if value.respond_to?(:each)
6
+ capture do
7
+ value.each do |v|
8
+ concat content_tag(:span, v, class: "value-part")
9
+ end
10
+ end
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ def format_value_for_form(record)
17
+ case record
18
+ when SystemSettings::StringListSetting
19
+ record.value.map { |v| v.gsub(SEPARATOR, "\\#{SEPARATOR}") }.join(SEPARATOR)
20
+ when SystemSettings::IntegerListSetting, SystemSettings::DecimalListSetting
21
+ record.value.join(SEPARATOR)
22
+ else
23
+ record.value
24
+ end
25
+ end
26
+
27
+ def display_settings_file_path
28
+ if SystemSettings.settings_file_path.to_s.include?(Rails.root.to_s)
29
+ Pathname.new(SystemSettings.settings_file_path).relative_path_from(Rails.root)
30
+ else
31
+ SystemSettings.settings_file_path
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SystemSettings
2
4
  class ApplicationRecord < ActiveRecord::Base
3
5
  self.abstract_class = true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SystemSettings
2
4
  class BooleanSetting < SystemSettings::Setting
3
5
  attribute :value, :boolean
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SystemSettings
2
4
  class Configurator
3
5
  class << self
@@ -53,20 +55,33 @@ module SystemSettings
53
55
  add(name, SystemSettings::BooleanSetting, value: value, description: description, &blk)
54
56
  end
55
57
 
56
- def persist
58
+ def decimal(name, value: nil, description: nil, &blk)
59
+ add(name, SystemSettings::DecimalSetting, value: value, description: description, &blk)
60
+ end
61
+
62
+ def decimal_list(name, value: nil, description: nil, &blk)
63
+ add(name, SystemSettings::DecimalListSetting, value: value || [], description: description, &blk)
64
+ end
65
+
66
+ def persist(only: [])
57
67
  SystemSettings.instrument("system_settings.persist", items: @items) do |payload|
58
68
  if settings_table_exists?
59
69
  SystemSettings::Setting.transaction do
60
- @items.each do |entry|
61
- persisted_record = SystemSettings::Setting.find_by(name: entry[:name])
62
- if persisted_record
63
- if persisted_record.class == entry[:class]
64
- persisted_record.update!(description: entry[:description])
65
- else
66
- warn "SystemSettings: Type mismatch detected! Previously #{entry[:name]} had type #{persisted_record.class.name} but you are loading #{entry[:class].name}"
70
+ if only.empty?
71
+ @items.each { |item| create_or_update_item(item) }
72
+ else
73
+ only.each do |wanted_name|
74
+ item = @items.find { |i| i[:name] == wanted_name } || begin
75
+ loaded_names = @items.empty? ? "(none)" : @items.map{ |i| i[:name] }.join("\n")
76
+ message = <<~MESSAGE.strip
77
+ Couldn't persist system setting #{wanted_name}. There are no items by this name. Could it be a typo?
78
+
79
+ Configurator has loaded following items:
80
+ #{loaded_names}
81
+ MESSAGE
82
+ raise(SystemSettings::Errors::NotLoadedError, message)
67
83
  end
68
- else
69
- entry[:class].create!(name: entry[:name], value: entry[:value], description: entry[:description])
84
+ create_or_update_item(item)
70
85
  end
71
86
  end
72
87
  end
@@ -104,5 +119,18 @@ module SystemSettings
104
119
  value = value.call if value.is_a?(Proc)
105
120
  @items.push(name: name, class: class_const, value: value, description: description)
106
121
  end
122
+
123
+ def create_or_update_item(item)
124
+ persisted_record = SystemSettings::Setting.find_by(name: item[:name])
125
+ if persisted_record
126
+ if persisted_record.class == item[:class]
127
+ persisted_record.update!(description: item[:description])
128
+ else
129
+ warn "SystemSettings: Type mismatch detected! Previously #{item[:name]} had type #{persisted_record.class.name} but you are loading #{item[:class].name}"
130
+ end
131
+ else
132
+ item[:class].create!(name: item[:name], value: item[:value], description: item[:description])
133
+ end
134
+ end
107
135
  end
108
136
  end