db_config 0.0.1 → 0.1.9
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/README.md +235 -0
- data/Rakefile +3 -0
- data/app/avo/resources/db_config.rb +121 -0
- data/app/controllers/avo/db_configs_controller.rb +7 -0
- data/lib/db_config/current.rb +9 -0
- data/lib/db_config/middleware.rb +15 -0
- data/lib/db_config/railtie.rb +40 -0
- data/lib/db_config/record.rb +38 -0
- data/lib/db_config/version.rb +3 -0
- data/lib/db_config.rb +293 -0
- data/lib/generators/db_config/install/install_generator.rb +83 -0
- data/lib/generators/db_config/install/templates/create_db_config.rb +15 -0
- data/lib/tasks/db_config_tasks.rake +4 -0
- metadata +70 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d5c54735150023b281a43c825dac7af70b17ff1f80ab1f5e38adf732b87f178
|
|
4
|
+
data.tar.gz: 3afc236097de31e5f932fdfcdc5399ec1cc26fa0e16068b654a272f7b6179591
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eda9e948efb6464e99541ed0e58a3cd7539e9491347075922a08f10aa006ce16f563dba38bce0a3370e09964e6e5c2a9576c3be9e7b3a31ec7cccf41505bd9dc
|
|
7
|
+
data.tar.gz: d211e5e4b58798e88dbfad1c02b1e402d1e4baf9417af1ebbca0c357175d571435e7d8eae94bc8e920321631e2b968f530da242b40d2aba4452f169d8fff0803
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Paul Bob
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# DBConfig
|
|
2
|
+
|
|
3
|
+
Database-backed configuration store for Rails with automatic type conversion, default values, and high-performance eager loading.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe storage**: Auto-detects and converts strings, integers, floats, booleans, arrays, hashes, and nil
|
|
8
|
+
|
|
9
|
+
- **Eager loading**: Cache frequently accessed configs for near-zero database overhead
|
|
10
|
+
- **Simple API**: `get`/`read`, `get!`, `set`/`write`, `update`, `delete`, `exist?`, `fetch` methods
|
|
11
|
+
|
|
12
|
+
## Installation & Setup
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Gemfile
|
|
16
|
+
gem "db_config"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
```bash
|
|
23
|
+
rails generate db_config:install
|
|
24
|
+
```
|
|
25
|
+
```bash
|
|
26
|
+
rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Avo Integration
|
|
30
|
+
|
|
31
|
+
> [!TIP]
|
|
32
|
+
> If you're using [Avo](https://avohq.io), this gem ships with a pre-configured resource out of the box that should be visible on the menu as "Configurations". If you're using Avo Pro with a custom menu, you can render the resource using:
|
|
33
|
+
> ```ruby
|
|
34
|
+
> resource :db_config
|
|
35
|
+
> ```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Set any data type - auto-detected and preserved
|
|
40
|
+
```ruby
|
|
41
|
+
DBConfig.set(:max_users, 1000)
|
|
42
|
+
DBConfig.write(:site_title, "My App") # write is an alias for set
|
|
43
|
+
DBConfig.set(:enabled, true)
|
|
44
|
+
DBConfig.set(:rate, 0.05)
|
|
45
|
+
DBConfig.set(:tags, ["ruby", "rails"])
|
|
46
|
+
DBConfig.set(:config, {api: "https://api.example.com", timeout: 30})
|
|
47
|
+
DBConfig.set(:feature, nil)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Get with type preservation
|
|
51
|
+
```ruby
|
|
52
|
+
DBConfig.get(:max_users) # => 1000 (Integer)
|
|
53
|
+
DBConfig.read(:enabled) # => true (Boolean) - alias for get
|
|
54
|
+
|
|
55
|
+
# Get missing config (safe - returns nil)
|
|
56
|
+
DBConfig.get(:missing_key) # => nil
|
|
57
|
+
|
|
58
|
+
# Get missing config with get! (raises error)
|
|
59
|
+
DBConfig.get!(:missing_key) # => raises DBConfig::NotFoundError
|
|
60
|
+
|
|
61
|
+
# Check if a config exists
|
|
62
|
+
DBConfig.exist?(:page_size) # => true or false
|
|
63
|
+
|
|
64
|
+
# Fetch with block - stores block result if key doesn't exist
|
|
65
|
+
DBConfig.fetch(:page_size) { 25 } # => 25 (stores if not found)
|
|
66
|
+
|
|
67
|
+
# Use || operator for fallback values
|
|
68
|
+
DBConfig.get(:page_size) || 25 # => 25 if :page_size not set
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Exception Handling
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# Strict get - raises exception if not found
|
|
76
|
+
begin
|
|
77
|
+
value = DBConfig.get!(:api_token)
|
|
78
|
+
rescue DBConfig::NotFoundError => e
|
|
79
|
+
Rails.logger.warn "Missing config: #{e.message}"
|
|
80
|
+
value = "default_token"
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Managing Configurations
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# Update configurations with type safety
|
|
88
|
+
DBConfig.set(:max_users, 1000) # Set value and type to Integer
|
|
89
|
+
DBConfig.update(:max_users, value: 500) # Update value keeping same type
|
|
90
|
+
|
|
91
|
+
DBConfig.set(:site_enabled, "true") # Set value and type to String
|
|
92
|
+
DBConfig.update(:site_enabled, type: "Boolean") # Convert "true" string to boolean true
|
|
93
|
+
|
|
94
|
+
DBConfig.set(:cache_ttl, 3600) # Set value and type to Integer
|
|
95
|
+
DBConfig.update(:cache_ttl, value: 4600, eager_load: true) # Update value and enable eager loading
|
|
96
|
+
|
|
97
|
+
DBConfig.delete(:old_setting) # => true if deleted, false if not found
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### ⚡ Eager Loading
|
|
101
|
+
|
|
102
|
+
> [!NOTE]
|
|
103
|
+
> Eager loaded configs are cached per request and automatically synced when changed
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Mark configs for eager loading (loaded once per request, cached)
|
|
107
|
+
DBConfig.set(:api_key, "secret123")
|
|
108
|
+
DBConfig.update(:api_key, eager_load: true) # Enable eager loading
|
|
109
|
+
|
|
110
|
+
# Now served from cache (no database query)
|
|
111
|
+
DBConfig.get(:api_key) # Served from cache
|
|
112
|
+
|
|
113
|
+
DBConfig.update(:api_key, eager_load: false) # Disable eager loading
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> [!TIP]
|
|
117
|
+
> **Good for**: API keys, site settings, frequently accessed configs
|
|
118
|
+
> **Avoid**: Rarely used configs, large data, user-specific settings
|
|
119
|
+
|
|
120
|
+
## Supported Types
|
|
121
|
+
|
|
122
|
+
Auto-detects and preserves: String, Integer, Float, Boolean, Array, Hash, nil
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# All types preserved exactly
|
|
126
|
+
DBConfig.set(:string, "hello") # => "hello" (String)
|
|
127
|
+
DBConfig.set(:integer, 42) # => 42 (Integer)
|
|
128
|
+
DBConfig.set(:float, 3.14) # => 3.14 (Float)
|
|
129
|
+
DBConfig.set(:boolean, true) # => true (Boolean)
|
|
130
|
+
DBConfig.set(:array, [1, 2, 3]) # => [1, 2, 3] (Array)
|
|
131
|
+
DBConfig.set(:hash, {a: 1}) # => {a: 1} (Hash)
|
|
132
|
+
DBConfig.set(:null, nil) # => nil (NilClass)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
|
|
137
|
+
### Reading Methods
|
|
138
|
+
| Method | Description | Raises Errors | Example |
|
|
139
|
+
|--------|-------------|---------------|---------|
|
|
140
|
+
| `get(key)` | Safe retrieval, returns nil if not found | No | `DBConfig.get(:api_key)` |
|
|
141
|
+
| `get!(key)` | Strict retrieval | Yes, NotFoundError | `DBConfig.get!(:api_key)` |
|
|
142
|
+
| `read(key)` | Alias for get() | No | `DBConfig.read(:api_key)` |
|
|
143
|
+
| `exist?(key)` | Check if key exists | No | `DBConfig.exist?(:api_key)` |
|
|
144
|
+
| `fetch(key, &block)` | Get or store block result | No | `DBConfig.fetch(:size) { 25 }` |
|
|
145
|
+
|
|
146
|
+
**Fallback Pattern:**
|
|
147
|
+
Use the `||` operator to provide fallback values when keys don't exist:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
# Recommended pattern for fallback values
|
|
151
|
+
page_size = DBConfig.get(:page_size) || 25
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Writing Methods
|
|
155
|
+
| Method | Description | Example |
|
|
156
|
+
|--------|-------------|---------|
|
|
157
|
+
| `set(key, value)` | Store configuration | `DBConfig.set(:api_key, "secret")` |
|
|
158
|
+
| `write(key, value)` | Alias for set() | `DBConfig.write(:api_key, "secret")` |
|
|
159
|
+
| `update(key, **options)` | Update existing config | `DBConfig.update(:key, value: 42)` |
|
|
160
|
+
| `delete(key)` | Remove configuration | `DBConfig.delete(:old_key)` |
|
|
161
|
+
|
|
162
|
+
### Method Details
|
|
163
|
+
|
|
164
|
+
#### `DBConfig.fetch(key, &block)`
|
|
165
|
+
Gets the value if it exists, or executes the block and stores the result if it doesn't.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# If key exists, returns existing value (block not executed)
|
|
169
|
+
DBConfig.set(:api_timeout, 30)
|
|
170
|
+
timeout = DBConfig.fetch(:api_timeout) { 60 } # => 30 (existing value)
|
|
171
|
+
|
|
172
|
+
# If key doesn't exist, executes block and stores result
|
|
173
|
+
cache_size = DBConfig.fetch(:cache_size) { 100 } # => 100 (stores 100)
|
|
174
|
+
cache_size = DBConfig.fetch(:cache_size) { 200 } # => 100 (returns stored value)
|
|
175
|
+
|
|
176
|
+
# Works with any data type
|
|
177
|
+
features = DBConfig.fetch(:features) { ["feature1", "feature2"] }
|
|
178
|
+
config = DBConfig.fetch(:api_config) { {endpoint: "api.com", timeout: 30} }
|
|
179
|
+
|
|
180
|
+
# Returns nil if no block given and key doesn't exist
|
|
181
|
+
DBConfig.fetch(:missing_key) # => nil
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `DBConfig.update(key, **options)`
|
|
185
|
+
Updates configuration entry with intelligent type conversion and validation.
|
|
186
|
+
|
|
187
|
+
**Parameters:**
|
|
188
|
+
- `key` (Symbol/String) - Configuration key (must exist)
|
|
189
|
+
- `**options` - Keyword arguments for updates:
|
|
190
|
+
- `value: Any` - New value to store (with type compatibility checking)
|
|
191
|
+
- `type: String` - Target type for conversion ("String", "Integer", "Float", "Boolean", "Array", "Hash", "NilClass")
|
|
192
|
+
- `eager_load: Boolean` - Enable/disable eager loading
|
|
193
|
+
|
|
194
|
+
**Returns:** The updated value in its final type
|
|
195
|
+
|
|
196
|
+
**Type Compatibility:**
|
|
197
|
+
- When updating `value`: New values can change type automatically (e.g., "hello" → 42 changes String to Integer)
|
|
198
|
+
- When updating `type`: Only compatible conversions are allowed (e.g., "123" → Integer, "hello" → Integer fails)
|
|
199
|
+
- Incompatible type conversions raise `ArgumentError` with detailed message
|
|
200
|
+
|
|
201
|
+
**Examples:**
|
|
202
|
+
```ruby
|
|
203
|
+
# Update value (can change type automatically)
|
|
204
|
+
DBConfig.set(:config_value, "hello")
|
|
205
|
+
DBConfig.update(:config_value, value: 42) # String → Integer (automatic)
|
|
206
|
+
|
|
207
|
+
# Change type explicitly (requires compatibility)
|
|
208
|
+
DBConfig.set(:enabled, "true") # "true" is set as a string
|
|
209
|
+
DBConfig.update(:enabled, type: "Boolean") # "true" → true (compatible conversion)
|
|
210
|
+
|
|
211
|
+
# Update multiple attributes
|
|
212
|
+
DBConfig.update(:cache_size, value: 1000, eager_load: true)
|
|
213
|
+
|
|
214
|
+
# Examples from the specification
|
|
215
|
+
DBConfig.update(:key, eager_load: true)
|
|
216
|
+
DBConfig.update(:key, type: "Boolean") # Only works if current value is compatible
|
|
217
|
+
DBConfig.update(:key, eager_load: true, value: true) # ✅ Always works
|
|
218
|
+
|
|
219
|
+
# Fails only on incompatible TYPE conversions (not value updates)
|
|
220
|
+
DBConfig.set(:username, "admin")
|
|
221
|
+
DBConfig.update(:username, type: "Integer") # ❌ Fails: "admin" can't become Integer
|
|
222
|
+
DBConfig.update(:username, value: 123) # ✅ Works: new value can be any type
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Raises:**
|
|
226
|
+
- `DBConfig::NotFoundError` if key doesn't exist
|
|
227
|
+
- `ArgumentError` for invalid types or incompatible conversions
|
|
228
|
+
|
|
229
|
+
## Contributing
|
|
230
|
+
|
|
231
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/avo-hq/db_config.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
class Avo::Resources::DbConfig < Avo::BaseResource
|
|
2
|
+
self.model_class = ::DBConfig::Record
|
|
3
|
+
self.title = :key
|
|
4
|
+
self.search = {
|
|
5
|
+
query: -> {
|
|
6
|
+
query.ransack(
|
|
7
|
+
key_cont: q,
|
|
8
|
+
id_cont: q,
|
|
9
|
+
value_cont: q,
|
|
10
|
+
value_type_cont: q,
|
|
11
|
+
eager_load_cont: q,
|
|
12
|
+
m: "or"
|
|
13
|
+
).result(distinct: false)
|
|
14
|
+
},
|
|
15
|
+
item: -> {
|
|
16
|
+
{
|
|
17
|
+
title: "",
|
|
18
|
+
description: "Key: <strong>#{record.key}</strong> <br> Value: <strong>#{record.value}</strong> <br> Type: <strong>#{record.value_type}</strong> <br> Eager Load: <strong>#{record.eager_load}</strong>".html_safe
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
self.description = -> {
|
|
24
|
+
base = if view.form?
|
|
25
|
+
"If you're not familiar with DBConfig, please refer to the <a href='https://github.com/avo-hq/db_config' target='_blank'>DBConfig</a> documentation.".html_safe
|
|
26
|
+
elsif view.index?
|
|
27
|
+
"This is used to manage the <a href='https://github.com/avo-hq/db_config' target='_blank'>DBConfig</a> configurations.<br>Search is performed on all fields.".html_safe
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if view.new?
|
|
31
|
+
base += "<br>This is a new configuration. Please fill in the key and the type values and click the 'Save' button.<br>
|
|
32
|
+
You'll be able to edit the value on the next page.".html_safe
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
base
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
VALUE_TYPE_CONVERSIONS = {
|
|
39
|
+
"String" => :text,
|
|
40
|
+
"Integer" => :number,
|
|
41
|
+
"Boolean" => :boolean,
|
|
42
|
+
"Hash" => :code,
|
|
43
|
+
"Array" => :text,
|
|
44
|
+
"Float" => :number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def fields
|
|
48
|
+
field :id, as: :id
|
|
49
|
+
field :key
|
|
50
|
+
|
|
51
|
+
if record.present?
|
|
52
|
+
field :value, as: VALUE_TYPE_CONVERSIONS[record.value_type], update_using: ->{
|
|
53
|
+
return value unless record.value_type == "Boolean"
|
|
54
|
+
|
|
55
|
+
case value
|
|
56
|
+
when "1"
|
|
57
|
+
"true"
|
|
58
|
+
when "0"
|
|
59
|
+
"false"
|
|
60
|
+
else
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
}, visible: -> { !resource.view.new? }
|
|
64
|
+
else
|
|
65
|
+
field :value, visible: -> { !resource.view.new? }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
field :value_type,
|
|
69
|
+
as: :select,
|
|
70
|
+
name: "Type",
|
|
71
|
+
options: DBConfig::Record::VALUE_TYPES, disabled: -> { record.persisted? },
|
|
72
|
+
help: -> {
|
|
73
|
+
return if !record.persisted?
|
|
74
|
+
|
|
75
|
+
path, data = Avo::Resources::DbConfig::ForceChangeType.link_arguments(resource: resource)
|
|
76
|
+
|
|
77
|
+
"Can't change the type of a configuration that has already been set.<br>
|
|
78
|
+
Click #{link_to("here", path, data: data).html_safe} to force the type change."
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
field :eager_load, as: :boolean
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.plural_name
|
|
85
|
+
"Configurations"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.singular_name
|
|
89
|
+
"Configuration"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class ForceChangeType < Avo::BaseAction
|
|
93
|
+
self.name = "Force Change Type"
|
|
94
|
+
self.message = "Are you sure you want to change the type of this configuration?<br>
|
|
95
|
+
This will reset the type to the selected type and will remove the current value."
|
|
96
|
+
self.confirm_button_label = "Force Change"
|
|
97
|
+
self.cancel_button_label = "Cancel"
|
|
98
|
+
self.visible = -> {
|
|
99
|
+
resource.view.form? && resource.record&.persisted?
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def fields
|
|
103
|
+
field :value_type, as: :select, options: DBConfig::Record::VALUE_TYPES
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
DEFAULT_VALUE_TYPES = {
|
|
107
|
+
"String" => "",
|
|
108
|
+
"Integer" => 0,
|
|
109
|
+
"Float" => 0.0,
|
|
110
|
+
"Boolean" => false,
|
|
111
|
+
"Array" => [],
|
|
112
|
+
"Hash" => {},
|
|
113
|
+
"NilClass" => nil
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def handle(query:, fields:, **)
|
|
117
|
+
query.first.update!(value_type: fields[:value_type], value: DEFAULT_VALUE_TYPES[fields[:value_type]])
|
|
118
|
+
succeed "Type changed successfully."
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This controller has been generated to enable Rails' resource routes.
|
|
2
|
+
# More information on https://docs.avohq.io/3.0/controllers.html
|
|
3
|
+
class Avo::DbConfigsController < Avo::ResourcesController
|
|
4
|
+
def after_create_path
|
|
5
|
+
edit_resource_path(record: @record, resource: @resource)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "rails/railtie"
|
|
2
|
+
require "db_config/middleware"
|
|
3
|
+
|
|
4
|
+
module DBConfig
|
|
5
|
+
class Railtie < ::Rails::Railtie
|
|
6
|
+
railtie_name :db_config
|
|
7
|
+
|
|
8
|
+
def self.root
|
|
9
|
+
@root ||= Pathname.new(File.expand_path('../..', __dir__))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rake_tasks do
|
|
13
|
+
load "tasks/db_config_tasks.rake"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
generators do
|
|
17
|
+
require "generators/db_config/install/install_generator"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
initializer "db_config.load_app_instance_data" do |app|
|
|
21
|
+
# This can be used for any setup that needs to happen after Rails is loaded
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
initializer "db_config.autoload" do |app|
|
|
25
|
+
if defined?(Avo)
|
|
26
|
+
db_config_app_directory = DBConfig::Railtie.root.join("app/avo").to_s
|
|
27
|
+
db_config_avo_controllers_directory = DBConfig::Railtie.root.join("app/controllers/avo").to_s
|
|
28
|
+
|
|
29
|
+
ActiveSupport::Dependencies.autoload_paths.delete(db_config_app_directory)
|
|
30
|
+
|
|
31
|
+
Rails.autoloaders.main.push_dir(db_config_app_directory, namespace: Avo)
|
|
32
|
+
Rails.autoloaders.main.push_dir(db_config_avo_controllers_directory, namespace: Avo)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
initializer "db_config.add_middleware" do |app|
|
|
37
|
+
app.middleware.use DBConfig::Middleware
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module DBConfig
|
|
2
|
+
class Record < ActiveRecord::Base
|
|
3
|
+
self.table_name_prefix = "db_config_"
|
|
4
|
+
|
|
5
|
+
VALUE_TYPES = %w[String Integer Float Boolean Array Hash NilClass]
|
|
6
|
+
|
|
7
|
+
validates :key, presence: true, uniqueness: true
|
|
8
|
+
validates :value_type, presence: true, inclusion: {in: VALUE_TYPES}
|
|
9
|
+
validates :eager_load, inclusion: {in: [true, false]}
|
|
10
|
+
|
|
11
|
+
# Sync cache automatically on any changes
|
|
12
|
+
after_save :sync_cache
|
|
13
|
+
after_destroy :sync_cache
|
|
14
|
+
|
|
15
|
+
def self.ransackable_attributes(auth_object = nil)
|
|
16
|
+
authorizable_ransackable_attributes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.ransackable_associations(auth_object = nil)
|
|
20
|
+
authorizable_ransackable_associations
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def sync_cache
|
|
26
|
+
# Only sync if Current attributes are available (i.e., in a request context)
|
|
27
|
+
return unless defined?(DBConfig::Current)
|
|
28
|
+
|
|
29
|
+
if destroyed?
|
|
30
|
+
# Remove from cache if destroyed
|
|
31
|
+
DBConfig::Current.cached_records.delete(key)
|
|
32
|
+
else
|
|
33
|
+
# Update cache with current record
|
|
34
|
+
DBConfig::Current.cached_records[key] = self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/db_config.rb
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
require "db_config/version"
|
|
2
|
+
require "db_config/railtie"
|
|
3
|
+
require "db_config/record"
|
|
4
|
+
require "db_config/current"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module DBConfig
|
|
8
|
+
class NotFoundError < StandardError; end
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def get(key)
|
|
12
|
+
record = get_record(key)
|
|
13
|
+
record ? convert_value(record.value, record.value_type) : nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get!(key)
|
|
17
|
+
record = get_record(key)
|
|
18
|
+
|
|
19
|
+
if record
|
|
20
|
+
convert_value(record.value, record.value_type)
|
|
21
|
+
else
|
|
22
|
+
raise NotFoundError, "DBConfig not found for key: #{key}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set(key, value)
|
|
27
|
+
value_str = serialize_value(value)
|
|
28
|
+
value_type = determine_type(value)
|
|
29
|
+
key = key.to_s
|
|
30
|
+
|
|
31
|
+
record = Current.cached_records[key] || DBConfig::Record.find_or_initialize_by(key:)
|
|
32
|
+
|
|
33
|
+
record.update!(
|
|
34
|
+
value: value_str,
|
|
35
|
+
value_type: value_type,
|
|
36
|
+
eager_load: record.eager_load || false
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
convert_value(record.value, record.value_type)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def delete(key)
|
|
43
|
+
key_str = key.to_s
|
|
44
|
+
record = DBConfig::Record.find_by(key: key_str)
|
|
45
|
+
|
|
46
|
+
if record
|
|
47
|
+
record.destroy!
|
|
48
|
+
true
|
|
49
|
+
else
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update(key, **kwargs)
|
|
55
|
+
key_str = key.to_s
|
|
56
|
+
record = get_record(key_str)
|
|
57
|
+
raise NotFoundError, "DBConfig not found for key: #{key}" unless record
|
|
58
|
+
|
|
59
|
+
# Extract current values
|
|
60
|
+
current_value = convert_value(record.value, record.value_type)
|
|
61
|
+
current_type = record.value_type
|
|
62
|
+
|
|
63
|
+
# Process updates
|
|
64
|
+
updates = {}
|
|
65
|
+
|
|
66
|
+
# Handle value update - new values can change type freely
|
|
67
|
+
if kwargs.key?(:value)
|
|
68
|
+
new_value = kwargs[:value]
|
|
69
|
+
new_type = determine_type(new_value)
|
|
70
|
+
|
|
71
|
+
updates[:value] = serialize_value(new_value)
|
|
72
|
+
updates[:value_type] = new_type
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Handle type update with compatibility check
|
|
76
|
+
if kwargs.key?(:type)
|
|
77
|
+
target_type = kwargs[:type]
|
|
78
|
+
|
|
79
|
+
# Validate target type
|
|
80
|
+
unless DBConfig::Record::VALUE_TYPES.include?(target_type)
|
|
81
|
+
raise ArgumentError, "Invalid type: #{target_type}. Must be one of: #{DBConfig::Record::VALUE_TYPES.join(", ")}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check if current value can be converted to target type
|
|
85
|
+
unless value_convertible_to_type?(current_value, current_type, target_type)
|
|
86
|
+
raise ArgumentError, "Can't modify type because value \"#{current_value}\" doesn't support conversion from \"#{current_type}\" to \"#{target_type}\""
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Convert value to new type
|
|
90
|
+
converted_value = convert_value_to_type(current_value, current_type, target_type)
|
|
91
|
+
updates[:value] = serialize_value(converted_value)
|
|
92
|
+
updates[:value_type] = target_type
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Handle eager_load update
|
|
96
|
+
if kwargs.key?(:eager_load)
|
|
97
|
+
updates[:eager_load] = kwargs[:eager_load]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Apply updates
|
|
101
|
+
record.update!(updates)
|
|
102
|
+
|
|
103
|
+
# Return the updated value in its new type
|
|
104
|
+
convert_value(record.value, record.value_type)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def exist?(key)
|
|
108
|
+
get_record(key) != nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def fetch(key, &block)
|
|
112
|
+
record = get_record(key)
|
|
113
|
+
|
|
114
|
+
if record
|
|
115
|
+
convert_value(record.value, record.value_type)
|
|
116
|
+
elsif block_given?
|
|
117
|
+
# Execute block and store the result
|
|
118
|
+
value = yield
|
|
119
|
+
set(key, value)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Return nil if key doesn't exist and no block given
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Aliases for convenience
|
|
126
|
+
alias_method :read, :get
|
|
127
|
+
alias_method :write, :set
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# Get record from cache or database, returns nil if not found
|
|
132
|
+
def get_record(key)
|
|
133
|
+
key = key.to_s
|
|
134
|
+
|
|
135
|
+
Current.cached_records[key] || DBConfig::Record.find_by(key:)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def determine_type(value)
|
|
139
|
+
case value
|
|
140
|
+
when NilClass
|
|
141
|
+
"NilClass"
|
|
142
|
+
when String
|
|
143
|
+
"String"
|
|
144
|
+
when Integer
|
|
145
|
+
"Integer"
|
|
146
|
+
when Float
|
|
147
|
+
"Float"
|
|
148
|
+
when TrueClass, FalseClass
|
|
149
|
+
"Boolean"
|
|
150
|
+
when Array
|
|
151
|
+
"Array"
|
|
152
|
+
when Hash
|
|
153
|
+
"Hash"
|
|
154
|
+
else
|
|
155
|
+
"String"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def serialize_value(value)
|
|
160
|
+
case value
|
|
161
|
+
when NilClass
|
|
162
|
+
nil
|
|
163
|
+
when Array, Hash
|
|
164
|
+
JSON.generate(value)
|
|
165
|
+
else
|
|
166
|
+
value.to_s
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def convert_value(value_str, value_type)
|
|
171
|
+
case value_type
|
|
172
|
+
when "NilClass"
|
|
173
|
+
nil
|
|
174
|
+
when "Boolean"
|
|
175
|
+
value_str == "true"
|
|
176
|
+
when "Integer"
|
|
177
|
+
value_str.to_i
|
|
178
|
+
when "Float"
|
|
179
|
+
value_str.to_f
|
|
180
|
+
when "Array"
|
|
181
|
+
JSON.parse(value_str)
|
|
182
|
+
when "Hash"
|
|
183
|
+
JSON.parse(value_str)
|
|
184
|
+
else
|
|
185
|
+
value_str
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def types_compatible?(current_value, current_type, new_value, new_type)
|
|
190
|
+
# If types are the same, always compatible
|
|
191
|
+
return true if current_type == new_type
|
|
192
|
+
|
|
193
|
+
# Allow specific compatible conversions
|
|
194
|
+
case [current_type, new_type]
|
|
195
|
+
when ["Boolean", "String"], ["Integer", "String"], ["Float", "String"], ["NilClass", "String"]
|
|
196
|
+
true
|
|
197
|
+
when ["String", "Boolean"]
|
|
198
|
+
["true", "false"].include?(current_value.to_s.downcase)
|
|
199
|
+
when ["String", "Integer"]
|
|
200
|
+
current_value.to_s.match?(/\A-?\d+\z/)
|
|
201
|
+
when ["String", "Float"]
|
|
202
|
+
current_value.to_s.match?(/\A-?\d*\.?\d+\z/)
|
|
203
|
+
when ["Integer", "Float"]
|
|
204
|
+
true
|
|
205
|
+
when ["Float", "Integer"]
|
|
206
|
+
current_value.to_f % 1 == 0 # Only if float is a whole number
|
|
207
|
+
when ["Boolean", "Integer"]
|
|
208
|
+
true # true -> 1, false -> 0
|
|
209
|
+
when ["Integer", "Boolean"]
|
|
210
|
+
[0, 1].include?(current_value)
|
|
211
|
+
else
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def value_convertible_to_type?(value, current_type, target_type)
|
|
217
|
+
# If types are the same, always convertible
|
|
218
|
+
return true if current_type == target_type
|
|
219
|
+
|
|
220
|
+
case target_type
|
|
221
|
+
when "String"
|
|
222
|
+
true # Everything can be converted to string
|
|
223
|
+
when "Boolean"
|
|
224
|
+
case current_type
|
|
225
|
+
when "String"
|
|
226
|
+
["true", "false", "1", "0"].include?(value.to_s.downcase)
|
|
227
|
+
when "Integer"
|
|
228
|
+
[0, 1].include?(value)
|
|
229
|
+
else
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
when "Integer"
|
|
233
|
+
case current_type
|
|
234
|
+
when "String"
|
|
235
|
+
value.to_s.match?(/\A-?\d+\z/)
|
|
236
|
+
when "Float"
|
|
237
|
+
value.to_f % 1 == 0
|
|
238
|
+
when "Boolean"
|
|
239
|
+
true
|
|
240
|
+
else
|
|
241
|
+
false
|
|
242
|
+
end
|
|
243
|
+
when "Float"
|
|
244
|
+
case current_type
|
|
245
|
+
when "String"
|
|
246
|
+
value.to_s.match?(/\A-?\d*\.?\d+\z/)
|
|
247
|
+
when "Integer", "Boolean"
|
|
248
|
+
true
|
|
249
|
+
else
|
|
250
|
+
false
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
false # Array, Hash, NilClass conversions not supported
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def convert_value_to_type(value, current_type, target_type)
|
|
258
|
+
return value if current_type == target_type
|
|
259
|
+
|
|
260
|
+
case target_type
|
|
261
|
+
when "String"
|
|
262
|
+
value.to_s
|
|
263
|
+
when "Boolean"
|
|
264
|
+
case current_type
|
|
265
|
+
when "String"
|
|
266
|
+
["true", "1"].include?(value.to_s.downcase)
|
|
267
|
+
when "Integer"
|
|
268
|
+
value == 1
|
|
269
|
+
end
|
|
270
|
+
when "Integer"
|
|
271
|
+
case current_type
|
|
272
|
+
when "String"
|
|
273
|
+
value.to_i
|
|
274
|
+
when "Float"
|
|
275
|
+
value.to_i
|
|
276
|
+
when "Boolean"
|
|
277
|
+
value ? 1 : 0
|
|
278
|
+
end
|
|
279
|
+
when "Float"
|
|
280
|
+
case current_type
|
|
281
|
+
when "String"
|
|
282
|
+
value.to_f
|
|
283
|
+
when "Integer"
|
|
284
|
+
value.to_f
|
|
285
|
+
when "Boolean"
|
|
286
|
+
value ? 1.0 : 0.0
|
|
287
|
+
end
|
|
288
|
+
else
|
|
289
|
+
raise ArgumentError, "Conversion to #{target_type} not supported"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module DbConfig
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
desc "Creates a DBConfig migration and shows usage information"
|
|
11
|
+
|
|
12
|
+
def copy_migration
|
|
13
|
+
migration_template "create_db_config.rb", File.join(db_migrate_path, "create_db_config.rb")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show_usage
|
|
17
|
+
say ""
|
|
18
|
+
say "=" * 70
|
|
19
|
+
say "DB_CONFIG INSTALLATION COMPLETE", :green
|
|
20
|
+
say "=" * 70
|
|
21
|
+
say ""
|
|
22
|
+
say "Run the migration:"
|
|
23
|
+
say " rails db:migrate", :yellow
|
|
24
|
+
say ""
|
|
25
|
+
say "Usage examples:", :green
|
|
26
|
+
say ""
|
|
27
|
+
say " # Set different types of configuration values"
|
|
28
|
+
say " DBConfig.set(:homepage_cta, 'Click here now!')", :cyan
|
|
29
|
+
say " DBConfig.write(:max_users, 1000) # alias for set", :cyan
|
|
30
|
+
say " DBConfig.set(:maintenance_mode, false)", :cyan
|
|
31
|
+
say " DBConfig.set(:allowed_countries, ['US', 'CA', 'UK'])", :cyan
|
|
32
|
+
say " DBConfig.set(:api_config, { endpoint: 'api.com', timeout: 30 })", :cyan
|
|
33
|
+
say ""
|
|
34
|
+
say " # Get configuration values (returns original data type)"
|
|
35
|
+
say " DBConfig.get(:homepage_cta)", :cyan
|
|
36
|
+
say " # => 'Click here now!'"
|
|
37
|
+
say " DBConfig.read(:allowed_countries) # alias for get", :cyan
|
|
38
|
+
say " # => ['US', 'CA', 'UK']"
|
|
39
|
+
say ""
|
|
40
|
+
say " # Check if configuration exists"
|
|
41
|
+
say " DBConfig.exist?(:page_size)", :cyan
|
|
42
|
+
say " # => true or false"
|
|
43
|
+
say ""
|
|
44
|
+
say " # Fetch with block - stores block result if key doesn't exist"
|
|
45
|
+
say " page_size = DBConfig.fetch(:page_size) { 25 }", :cyan
|
|
46
|
+
say " debug_mode = DBConfig.fetch(:debug_mode) { false }", :cyan
|
|
47
|
+
say ""
|
|
48
|
+
say " # Use || operator for fallback values"
|
|
49
|
+
say " page_size = DBConfig.get(:page_size) || 25", :cyan
|
|
50
|
+
say " admin_emails = DBConfig.get(:admin_emails) || []", :cyan
|
|
51
|
+
say ""
|
|
52
|
+
|
|
53
|
+
say " # Enable/disable eager loading for a config"
|
|
54
|
+
say " DBConfig.eager_load(:homepage_cta, true)", :cyan
|
|
55
|
+
say ""
|
|
56
|
+
say " # Handle missing configs"
|
|
57
|
+
say " begin"
|
|
58
|
+
say " DBConfig.get(:missing_key)"
|
|
59
|
+
say " rescue DBConfig::NotFoundError => e"
|
|
60
|
+
say " puts e.message"
|
|
61
|
+
say " end", :cyan
|
|
62
|
+
say ""
|
|
63
|
+
say "Supported data types: String, Integer, Float, Boolean, Array, Hash", :green
|
|
64
|
+
say ""
|
|
65
|
+
say "=" * 70
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def db_migrate_path
|
|
71
|
+
configured_migrate_path || default_migrate_path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def configured_migrate_path
|
|
75
|
+
Rails.application.config.paths["db/migrate"]&.first
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def default_migrate_path
|
|
79
|
+
Rails.root.join("db", "migrate")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateDbConfig < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" if ActiveRecord::Migration.respond_to?(:current_version) %>
|
|
2
|
+
def change
|
|
3
|
+
create_table :db_config_records do |t|
|
|
4
|
+
t.string :key, null: false
|
|
5
|
+
t.string :value
|
|
6
|
+
t.string :value_type, null: false, default: "String"
|
|
7
|
+
t.boolean :eager_load, null: false, default: false
|
|
8
|
+
|
|
9
|
+
t.timestamps null: false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :db_config_records, :key, unique: true
|
|
13
|
+
add_index :db_config_records, :eager_load
|
|
14
|
+
end
|
|
15
|
+
end
|
metadata
CHANGED
|
@@ -1,43 +1,93 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: db_config
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
autorequire:
|
|
7
|
+
- Paul Bob
|
|
8
|
+
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2026-02-13 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
14
|
+
name: activerecord
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
16
|
requirements:
|
|
18
|
-
- -
|
|
17
|
+
- - ">="
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
|
-
version:
|
|
19
|
+
version: '6.0'
|
|
21
20
|
type: :runtime
|
|
22
21
|
prerelease: false
|
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
23
|
requirements:
|
|
25
|
-
- -
|
|
24
|
+
- - ">="
|
|
26
25
|
- !ruby/object:Gem::Version
|
|
27
|
-
version:
|
|
28
|
-
|
|
26
|
+
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: railties
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '6.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '6.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activesupport
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '6.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '6.0'
|
|
55
|
+
description: |
|
|
56
|
+
DBConfig provides a powerful, database-backed configuration store for Rails applications.
|
|
57
|
+
Store and retrieve configuration values dynamically with automatic type detection and conversion
|
|
58
|
+
(strings, integers, floats, booleans, arrays, hashes, and nil). Features eager loading for
|
|
59
|
+
high-performance access to frequently used configs, a simple API (get/set/update/delete),
|
|
60
|
+
and seamless integration with Avo admin panels.
|
|
61
|
+
|
|
62
|
+
See https://github.com/avo-hq/db_config for full documentation and usage examples.
|
|
29
63
|
email:
|
|
30
|
-
- adrian@adrianthedev.com
|
|
31
64
|
- paul.ionut.bob@gmail.com
|
|
32
65
|
executables: []
|
|
33
66
|
extensions: []
|
|
34
67
|
extra_rdoc_files: []
|
|
35
|
-
files:
|
|
36
|
-
|
|
68
|
+
files:
|
|
69
|
+
- MIT-LICENSE
|
|
70
|
+
- README.md
|
|
71
|
+
- Rakefile
|
|
72
|
+
- app/avo/resources/db_config.rb
|
|
73
|
+
- app/controllers/avo/db_configs_controller.rb
|
|
74
|
+
- lib/db_config.rb
|
|
75
|
+
- lib/db_config/current.rb
|
|
76
|
+
- lib/db_config/middleware.rb
|
|
77
|
+
- lib/db_config/railtie.rb
|
|
78
|
+
- lib/db_config/record.rb
|
|
79
|
+
- lib/db_config/version.rb
|
|
80
|
+
- lib/generators/db_config/install/install_generator.rb
|
|
81
|
+
- lib/generators/db_config/install/templates/create_db_config.rb
|
|
82
|
+
- lib/tasks/db_config_tasks.rake
|
|
83
|
+
homepage: https://github.com/avo-hq/db_config
|
|
37
84
|
licenses:
|
|
38
|
-
-
|
|
39
|
-
metadata:
|
|
40
|
-
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
homepage_uri: https://github.com/avo-hq/db_config
|
|
88
|
+
source_code_uri: https://github.com/avo-hq/db_config
|
|
89
|
+
changelog_uri: https://github.com/avo-hq/db_config/blob/main/CHANGELOG.md
|
|
90
|
+
post_install_message:
|
|
41
91
|
rdoc_options: []
|
|
42
92
|
require_paths:
|
|
43
93
|
- lib
|
|
@@ -53,7 +103,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
53
103
|
version: '0'
|
|
54
104
|
requirements: []
|
|
55
105
|
rubygems_version: 3.5.22
|
|
56
|
-
signing_key:
|
|
106
|
+
signing_key:
|
|
57
107
|
specification_version: 4
|
|
58
|
-
summary:
|
|
108
|
+
summary: Database-backed configuration store for Rails with automatic type conversion,
|
|
109
|
+
eager loading, and Avo integration
|
|
59
110
|
test_files: []
|