rails-settings-cached-rails-admin 0.1.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.
@@ -0,0 +1,375 @@
1
+ require "rails_admin/config/actions/base"
2
+
3
+ module RailsAdminSettingsUi
4
+ class SettingsUi < RailsAdmin::Config::Actions::Base
5
+ register_instance_option :root? do
6
+ true
7
+ end
8
+
9
+ register_instance_option :breadcrumb_parent do
10
+ nil
11
+ end
12
+
13
+ register_instance_option :controller do
14
+ proc do
15
+
16
+ def build_settings_data
17
+ unless defined?(Setting)
18
+ Rails.logger.warn "Setting class not found!"
19
+ return {}
20
+ end
21
+
22
+ settings_class = Setting
23
+
24
+ # Verify the Setting class has the methods we need
25
+ Rails.logger.info "=== SETTING CLASS VERIFICATION ==="
26
+ Rails.logger.info "Setting class: #{settings_class}"
27
+ Rails.logger.info "Setting ancestors: #{settings_class.ancestors.map(&:name)}"
28
+ Rails.logger.info "Setting responds to field methods? #{settings_class.respond_to?(:field)}"
29
+
30
+ # Debug: Let's see what methods are available
31
+ Rails.logger.info "=== DEBUG: Setting class methods ==="
32
+ Rails.logger.info "Available methods: #{settings_class.methods.grep(/default|field|_field/).sort}"
33
+ Rails.logger.info "Singleton methods: #{settings_class.singleton_methods.grep(/default|field|_field/).sort}"
34
+
35
+ # Handle different versions of rails-settings-cached
36
+ defaults = if settings_class.respond_to?(:get_defaults)
37
+ Rails.logger.info "Using get_defaults"
38
+ settings_class.get_defaults
39
+ elsif settings_class.respond_to?(:defaults)
40
+ Rails.logger.info "Using defaults"
41
+ settings_class.defaults
42
+ elsif settings_class.respond_to?(:_defaults)
43
+ Rails.logger.info "Using _defaults"
44
+ settings_class._defaults
45
+ elsif settings_class.respond_to?(:defined_fields)
46
+ Rails.logger.info "Using defined_fields"
47
+ settings_class.defined_fields
48
+ elsif settings_class.respond_to?(:_defined_fields)
49
+ Rails.logger.info "Using _defined_fields"
50
+ settings_class._defined_fields
51
+ else
52
+ Rails.logger.info "No default method found, trying to extract from class"
53
+ # Try to get field definitions from class variables or constants
54
+ if settings_class.class_variables.any?
55
+ Rails.logger.info "Class variables: #{settings_class.class_variables}"
56
+ end
57
+ if settings_class.constants.any?
58
+ Rails.logger.info "Constants: #{settings_class.constants}"
59
+ end
60
+ {}
61
+ end
62
+
63
+ Rails.logger.info "Defaults found: #{defaults}"
64
+ Rails.logger.info "Defaults class: #{defaults.class}"
65
+
66
+ current_values = {}
67
+
68
+ # Handle different formats of defaults (Hash vs Array)
69
+ if defaults.is_a?(Hash)
70
+ # Handle Hash format: {key: default_value}
71
+ defaults.each_key do |key|
72
+ current_values[key] = settings_class.public_send(key)
73
+ end
74
+
75
+ # Group settings by category (based on key prefix or custom logic)
76
+ grouped_settings = {}
77
+ defaults.each do |key, default_value|
78
+ category = extract_category(key)
79
+ grouped_settings[category] ||= []
80
+
81
+ field_type = determine_field_type(default_value, current_values[key])
82
+
83
+ grouped_settings[category] << {
84
+ key: key,
85
+ label: key.to_s.humanize,
86
+ default_value: default_value,
87
+ current_value: current_values[key],
88
+ field_type: field_type,
89
+ description: nil
90
+ }
91
+ end
92
+ elsif defaults.is_a?(Array)
93
+ # Handle Array format: might be array of field names or field objects
94
+ Rails.logger.info "Processing Array format defaults"
95
+ grouped_settings = {}
96
+
97
+ defaults.each do |field_info|
98
+ Rails.logger.info "Field info: #{field_info} (#{field_info.class})"
99
+
100
+ # Try to extract key and default value from different possible formats
101
+ key = nil
102
+ default_value = nil
103
+
104
+ if field_info.is_a?(Symbol) || field_info.is_a?(String)
105
+ # Simple field name
106
+ key = field_info.to_sym
107
+ default_value = settings_class.public_send(key) rescue nil
108
+ elsif field_info.is_a?(Hash)
109
+ # Hash with field info
110
+ key = field_info[:name] || field_info['name'] || field_info.keys.first
111
+ default_value = field_info[:default] || field_info['default'] || field_info.values.first
112
+ elsif field_info.respond_to?(:key) && field_info.respond_to?(:default)
113
+ # RailsSettings::Fields object with key and default
114
+ key = field_info.key
115
+ default_value = field_info.default
116
+ elsif field_info.respond_to?(:name)
117
+ # Object with name method
118
+ key = field_info.name
119
+ default_value = field_info.respond_to?(:default) ? field_info.default : nil
120
+ end
121
+
122
+ Rails.logger.info "Extracted key: #{key}, default_value: #{default_value}"
123
+
124
+ if key
125
+ key = key.to_sym
126
+ current_values[key] = settings_class.public_send(key) rescue default_value
127
+
128
+ category = extract_category(key)
129
+ grouped_settings[category] ||= []
130
+
131
+ field_type = determine_field_type(default_value, current_values[key])
132
+
133
+ Rails.logger.info "Adding setting: #{key} to category: #{category}, field_type: #{field_type}"
134
+
135
+ grouped_settings[category] << {
136
+ key: key,
137
+ label: key.to_s.humanize,
138
+ default_value: default_value,
139
+ current_value: current_values[key],
140
+ field_type: field_type,
141
+ description: nil
142
+ }
143
+ else
144
+ Rails.logger.info "Key extraction failed for field_info: #{field_info}"
145
+ end
146
+ end
147
+ else
148
+ Rails.logger.info "Unknown defaults format: #{defaults.class}"
149
+ grouped_settings = {}
150
+ end
151
+
152
+ Rails.logger.info "Final grouped_settings: #{grouped_settings}"
153
+ Rails.logger.info "Grouped settings keys: #{grouped_settings.keys}"
154
+
155
+ grouped_settings
156
+ end
157
+
158
+ def update_single_setting
159
+ return unless defined?(Setting) && params[:setting_key] && params[:setting_value]
160
+
161
+ key = params[:setting_key]
162
+ value = params[:setting_value]
163
+
164
+ Rails.logger.info "=== UPDATING SINGLE SETTING ==="
165
+ Rails.logger.info "Key: #{key}, Value: #{value}"
166
+
167
+ begin
168
+ # Convert value based on the original type
169
+ converted_value = convert_value(key, value)
170
+
171
+ Rails.logger.info "Setting #{key}: #{value} -> #{converted_value} (#{converted_value.class})"
172
+
173
+ # Use direct ActiveRecord approach to bypass full model validation
174
+ # This prevents validation errors from other unrelated settings
175
+ setting_record = Setting.find_or_initialize_by(var: key)
176
+
177
+ # Serialize the value properly based on the rails-settings-cached format
178
+ serialized_value = case converted_value
179
+ when String
180
+ converted_value
181
+ when NilClass
182
+ nil
183
+ else
184
+ # Use YAML serialization for complex types (same as rails-settings-cached)
185
+ converted_value.to_yaml
186
+ end
187
+
188
+ # Rails.logger.info "Serialized value: #{serialized_value}"
189
+
190
+ # Update directly without triggering full model validations
191
+ if setting_record.persisted?
192
+ result = setting_record.update_column(:value, serialized_value)
193
+ # Rails.logger.info "Updated existing record: #{result}"
194
+ else
195
+ setting_record.value = serialized_value
196
+ result = setting_record.save(validate: false) # Skip validations to avoid cross-field validation errors
197
+ # Rails.logger.info "Created new record: #{result}"
198
+ end
199
+
200
+ # Clear the settings cache so the new value is loaded
201
+ if Setting.respond_to?(:clear_cache)
202
+ Setting.clear_cache
203
+ elsif Setting.respond_to?(:reload!)
204
+ Setting.reload!
205
+ end
206
+
207
+ # Verify the setting was actually updated
208
+ new_value = Setting.public_send(key)
209
+ # Rails.logger.info "Verification: #{key} is now #{new_value}"
210
+
211
+ return { success: true, message: "Setting updated successfully", new_value: new_value }
212
+
213
+ rescue => e
214
+ Rails.logger.error "Failed to update setting #{key}: #{e.message}"
215
+ Rails.logger.error e.backtrace.join("\n")
216
+ return { success: false, error: e.message }
217
+ end
218
+ end
219
+
220
+ def extract_category(key)
221
+ # Try to extract category from key (e.g., 'mail_from' -> 'Mail', 'api_key' -> 'API')
222
+ parts = key.to_s.split('_')
223
+ if parts.length > 1
224
+ parts.first.humanize
225
+ else
226
+ 'General'
227
+ end
228
+ end
229
+
230
+ def determine_field_type(default_value, current_value)
231
+ value = current_value || default_value
232
+
233
+ case value
234
+ when TrueClass, FalseClass
235
+ :boolean
236
+ when Integer
237
+ :integer
238
+ when Float
239
+ :float
240
+ when Array
241
+ :array
242
+ when Hash
243
+ :json
244
+ else
245
+ # Check if it looks like a long text
246
+ if value.to_s.length > 100 || value.to_s.include?("\n")
247
+ :text
248
+ elsif value.to_s.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
249
+ :email
250
+ elsif value.to_s.match?(/\Ahttps?:\/\//)
251
+ :url
252
+ else
253
+ :string
254
+ end
255
+ end
256
+ end
257
+
258
+
259
+
260
+ def convert_value(key, value)
261
+ # Handle blank values
262
+ return nil if value.nil?
263
+ return "" if value == ""
264
+
265
+ # Get the original type from defaults using the same approach as build_settings_data
266
+ defaults = if Setting.respond_to?(:get_defaults)
267
+ Setting.get_defaults
268
+ elsif Setting.respond_to?(:defaults)
269
+ Setting.defaults
270
+ elsif Setting.respond_to?(:_defaults)
271
+ Setting._defaults
272
+ else
273
+ {}
274
+ end
275
+
276
+ original_value = defaults[key.to_sym] || defaults[key.to_s]
277
+
278
+ case original_value
279
+ when TrueClass, FalseClass
280
+ # Handle checkbox values - they come as "1" for checked, "0" for unchecked
281
+ if value.is_a?(Array) && value.size == 2
282
+ # Rails checkbox helper sends ["0", "1"] when checked, ["0"] when unchecked
283
+ value.include?("1")
284
+ else
285
+ value == '1' || value == 'true' || value == true
286
+ end
287
+ when Integer
288
+ value.to_s.strip.empty? ? 0 : value.to_i
289
+ when Float
290
+ value.to_s.strip.empty? ? 0.0 : value.to_f
291
+ when Array
292
+ if value.is_a?(Array)
293
+ value
294
+ else
295
+ value.to_s.split(',').map(&:strip).reject(&:empty?)
296
+ end
297
+ when Hash
298
+ if value.is_a?(Hash)
299
+ value
300
+ else
301
+ JSON.parse(value.to_s)
302
+ end
303
+ else
304
+ value.to_s
305
+ end
306
+ rescue JSON::ParserError => e
307
+ Rails.logger.error "JSON parsing error for key #{key}: #{e.message}"
308
+ value.to_s
309
+ end
310
+
311
+ Rails.logger.info "=== SETTINGS ACTION ==="
312
+ Rails.logger.info "Request method: #{request.method}"
313
+ Rails.logger.info "Request path: #{request.path}"
314
+ Rails.logger.info "Params: #{params.inspect}"
315
+ Rails.logger.info "AJAX request: #{request.xhr?}"
316
+
317
+ @settings_data = build_settings_data
318
+
319
+ if request.post?
320
+ Rails.logger.info "Processing POST request for settings update"
321
+
322
+ # Handle individual setting update (AJAX)
323
+ if request.xhr? && params[:setting_key] && params[:setting_value]
324
+ Rails.logger.info "Processing individual setting update via AJAX"
325
+ result = update_single_setting
326
+ render json: result
327
+ else
328
+ Rails.logger.info "No individual setting update parameters found"
329
+ # Fallback for non-AJAX requests or invalid parameters
330
+ render json: { success: false, error: "Invalid request parameters" }, status: 400
331
+ end
332
+ else
333
+ Rails.logger.info "Rendering settings page with #{@settings_data.size} categories"
334
+ render template: 'rails_admin_settings_ui/settings/index'
335
+ end
336
+ end
337
+ end
338
+
339
+ register_instance_option :route_fragment do
340
+ 'settings'
341
+ end
342
+
343
+ register_instance_option :link_icon do
344
+ 'fa fa-cog'
345
+ end
346
+
347
+ register_instance_option :http_methods do
348
+ [:get, :post]
349
+ end
350
+
351
+ register_instance_option :visible? do
352
+ true
353
+ end
354
+
355
+ register_instance_option :action_name do
356
+ 'settings_ui'
357
+ end
358
+
359
+ # Set proper title with fallback
360
+ register_instance_option :title do
361
+ I18n.t('admin.actions.settings_ui.title', default: 'Settings')
362
+ end
363
+
364
+ # Set proper menu label with fallback
365
+ register_instance_option :menu_label do
366
+ I18n.t('admin.actions.settings_ui.menu', default: 'Settings')
367
+ end
368
+
369
+ # Set breadcrumb text with fallback
370
+ register_instance_option :breadcrumb_text do
371
+ I18n.t('admin.actions.settings_ui.breadcrumb', default: 'Settings')
372
+ end
373
+
374
+ end
375
+ end
@@ -0,0 +1,3 @@
1
+ module RailsAdminSettingsUi
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "rails_admin_settings_ui/version"
2
+ require_relative "rails_admin_settings_ui/engine"
3
+ require_relative "rails_admin_settings_ui/railtie"
4
+
5
+ module RailsAdminSettingsUi
6
+ class Error < StandardError; end
7
+
8
+ # Register the action immediately if Rails Admin is available
9
+ if defined?(RailsAdmin) && defined?(RailsAdmin::Config::Actions)
10
+ RailsAdmin::Config::Actions.register(:settings_ui, RailsAdminSettingsUi::SettingsUi)
11
+ end
12
+ end
data/screenshot.png ADDED
Binary file
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-settings-cached-rails-admin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Roman Klevtsov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails_admin
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails-settings-cached
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description: Provides a clean, intuitive UI for managing application settings in Rails
98
+ Admin, integrating seamlessly with rails-settings-cached gem
99
+ email:
100
+ - frontandstart@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".cursor/rules"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - MANUAL_SETUP.md
109
+ - README.md
110
+ - Rakefile
111
+ - app/views/rails_admin_settings_ui/settings/index.html.erb
112
+ - example_app_setup.md
113
+ - lib/locales/en.yml
114
+ - lib/rails_admin_settings_ui.rb
115
+ - lib/rails_admin_settings_ui/engine.rb
116
+ - lib/rails_admin_settings_ui/helper.rb
117
+ - lib/rails_admin_settings_ui/railtie.rb
118
+ - lib/rails_admin_settings_ui/settings_ui.rb
119
+ - lib/rails_admin_settings_ui/version.rb
120
+ - screenshot.png
121
+ homepage: https://github.com/r3cha/rails-settings-cache-rails-admin
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ homepage_uri: https://github.com/r3cha/rails-settings-cache-rails-admin
126
+ source_code_uri: https://github.com/r3cha/rails-settings-cache-rails-admin
127
+ changelog_uri: https://github.com/r3cha/rails-settings-cache-rails-admin/blob/main/CHANGELOG.md
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 2.7.0
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.5.22
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: User-friendly settings interface for Rails Admin with rails-settings-cached
147
+ test_files: []