activerecord-deepstore 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +129 -0
- data/Rakefile +16 -0
- data/activerecord-deepstore.gemspec +36 -0
- data/activerecord-deepstore.sqlite3 +0 -0
- data/config/database.yml +17 -0
- data/db/activerecord-deepstore.sqlite3 +0 -0
- data/lib/active_record/deepstore/version.rb +7 -0
- data/lib/active_record/deepstore.rb +230 -0
- data/sig/activerecord/deepstore.rbs +6 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8a8e192500276489b18a179c56d5e0878a1ca8b4600b85cd2c4f587403f2dc01
|
4
|
+
data.tar.gz: d40765f8d041804eb22d02036f7cb458e8540d7a75a7bfc82c5176af189ccfe2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 31153e6a0a5e32e49cd5cc8667cb79681b21d8c296d1521a7cdbf8b264b681552953ca5ee4c9061435cc880044fbf86f530f99ef6d795ef3a7ea7c1db1b3188b
|
7
|
+
data.tar.gz: a8e5052ee7ff97e8ac527c693019cad85a32430248984adb841e2be1ac49ae2279c060732fd0858620bc4c3ba3748b4c6622079bef7ba860eeccab8168d88f40
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
AllCops:
|
2
|
+
SuggestExtensions: false
|
3
|
+
TargetRubyVersion: 2.6
|
4
|
+
|
5
|
+
Metrics/LineLength:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Metrics/BlockLength:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Metrics/ClassLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics/ModuleLength:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/StringLiterals:
|
18
|
+
Enabled: true
|
19
|
+
EnforcedStyle: double_quotes
|
20
|
+
|
21
|
+
Style/StringLiteralsInInterpolation:
|
22
|
+
Enabled: true
|
23
|
+
EnforcedStyle: double_quotes
|
24
|
+
|
25
|
+
Layout/LineLength:
|
26
|
+
Max: 120
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Emmanuel Cousin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# ActiveRecord-Deepstore
|
2
|
+
|
3
|
+
ActiveRecord-Deepstore is a Ruby gem that extends ActiveRecord models with additional functionality for handling deeply nested data structures within a database column. It simplifies storing, accessing, and managing complex nested data in your Rails applications.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'activerecord-deepstore'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install activerecord-deepstore
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
To use ActiveRecord-Deepstore in your Rails application, include it in your ActiveRecord models:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class MyModel < ActiveRecord::Base
|
31
|
+
extend ActiveRecord::Deepstore
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Once included, your models gain access to methods for storing and accessing deeply nested data within database columns.
|
36
|
+
|
37
|
+
### Example
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class User < ActiveRecord::Base
|
41
|
+
extend ActiveRecord::Deepstore
|
42
|
+
|
43
|
+
deep_store :settings, {
|
44
|
+
notifications: {
|
45
|
+
posts: { push: true, email: true },
|
46
|
+
comments: { push: true, email: false }
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
This implementation provides with an accessor for every level of the provider hash:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
user.notifications_settings # => { posts: { push: true, email: true }, comments: { push: true, email: false } }
|
56
|
+
user.posts_notifications_settings # => { push: true, email: true }
|
57
|
+
user.push_posts_notifications_settings # => true
|
58
|
+
user.email_posts_notifications_settings # => true
|
59
|
+
user.comments_notifications_settings # => { push: true, email: false }
|
60
|
+
user.push_comments_notifications_settings # => true
|
61
|
+
user.email_comments_notifications_settings # => false
|
62
|
+
```
|
63
|
+
|
64
|
+
You can list all the generated accessors by calling `deep_stored_accessors` on the model `User`:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
User.deep_stored_accessors
|
68
|
+
# => ["notifications_settings", "posts_notifications_settings", "push_posts_notifications_settings", [...], "email_comments_notifications_settings"]
|
69
|
+
```
|
70
|
+
|
71
|
+
#### Automatic typecasting
|
72
|
+
|
73
|
+
Writer methods automatically cast the value to the type the default values belong to. For example:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
user.push_comments_notifications_settings = "1" # => "1"
|
77
|
+
user.push_comments_notifications_settings # => true
|
78
|
+
user.push_comments_notifications_settings = "0" # => "0"
|
79
|
+
user.push_comments_notifications_settings # => false
|
80
|
+
```
|
81
|
+
|
82
|
+
#### Tracking value changes
|
83
|
+
|
84
|
+
Dirty attributes are implemented for every accessor. For example:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
user.push_comments_notifications_settings # => false
|
88
|
+
user.push_comments_notifications_settings = true # => true
|
89
|
+
user.push_comments_notifications_settings_was # => false
|
90
|
+
user.push_comments_notifications_settings_changes # => { false => true }
|
91
|
+
user.push_comments_notifications_settings_changed? # => true
|
92
|
+
```
|
93
|
+
|
94
|
+
#### Accessing default values
|
95
|
+
|
96
|
+
You can access the default value of every accessor at anytime by calling the associated `default_#{accessor_name}` method:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
user.push_comments_notifications_settings # => false
|
100
|
+
user.update! push_comments_notifications_settings: true
|
101
|
+
user.push_comments_notifications_settings # => true
|
102
|
+
user.default_push_comments_notifications_settings #=> false
|
103
|
+
```
|
104
|
+
|
105
|
+
#### Resetting to default values
|
106
|
+
|
107
|
+
You can reset every accessor to its default value at anytime by calling the associated `reset_#{accessor_name}` method:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# When the changes are not persisted
|
111
|
+
user.push_comments_notifications_settings # => false
|
112
|
+
user.push_comments_notifications_settings = true
|
113
|
+
user.reset_push_comments_notifications_settings
|
114
|
+
user.push_comments_notifications_settings # => false
|
115
|
+
|
116
|
+
# When the changes are persisted
|
117
|
+
user.update! push_comments_notifications_settings: true
|
118
|
+
user.push_comments_notifications_settings # => true
|
119
|
+
user.reset_push_comments_notifications_settings!
|
120
|
+
user.reload.push_comments_notifications_settings # => false
|
121
|
+
```
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/EmCousin/activerecord-deepstore](https://github.com/EmCousin/activerecord-deepstore).
|
126
|
+
|
127
|
+
## License
|
128
|
+
|
129
|
+
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,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/active_record/deepstore/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "activerecord-deepstore"
|
7
|
+
spec.version = ActiveRecord::Deepstore::VERSION
|
8
|
+
spec.authors = ["Emmanuel Cousin"]
|
9
|
+
spec.email = ["emmanuel@hey.com"]
|
10
|
+
|
11
|
+
spec.summary = "ActiveRecord-Deepstore adds powerful functionality to ActiveRecord models for handling deeply nested data structures within database columns. Simplify storing, accessing, and managing complex nested data in your Rails applications with ease."
|
12
|
+
spec.description = "ActiveRecord-Deepstore enhances ActiveRecord models with powerful functionality for handling deeply nested data structures within a database column. It provides methods for storing, accessing, and managing deeply nested data, making it easier to work with complex data structures in your Rails applications. With ActiveRecord-Deepstore, you can seamlessly store nested hashes in database columns, access nested data with simple method calls, track changes to nested attributes, and much more. This gem simplifies the handling of complex data structures, improving the maintainability and readability of your Rails codebase."
|
13
|
+
spec.homepage = "https://github.com/EmCousin/activerecord-deepstore"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(File.expand_path(f) == __FILE__) ||
|
27
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_dependency "activerecord", "~> 7.0"
|
35
|
+
spec.add_dependency "activesupport", "~> 7.0"
|
36
|
+
end
|
File without changes
|
data/config/database.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ":memory:"
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql2
|
7
|
+
database: active_record_deepstore_test
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
|
11
|
+
postgresql:
|
12
|
+
adapter: postgresql
|
13
|
+
database: active_record_deepstore_test
|
14
|
+
username: postgres
|
15
|
+
password:
|
16
|
+
host: localhost
|
17
|
+
port: 5432
|
Binary file
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "deepstore/version"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
# The ActiveRecord::Deepstore module extends ActiveRecord models with additional
|
7
|
+
# functionality for handling deeply nested data structures within a database column.
|
8
|
+
module Deepstore
|
9
|
+
# Raised when an error occurs in the ActiveRecord::Deepstore module.
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :deep_stored_accessors
|
13
|
+
|
14
|
+
# Retrieves or initializes the array containing names of attributes declared as deep stores.
|
15
|
+
#
|
16
|
+
# @return [Array<String>] The array containing names of deep stores.
|
17
|
+
def deep_stores
|
18
|
+
@deep_stores ||= []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Recursively traverses a nested hash and returns a flattened representation of leaf nodes along with their paths.
|
22
|
+
#
|
23
|
+
# @param hash [Hash] The nested hash to traverse.
|
24
|
+
# @param path [Array] The current path in the hash.
|
25
|
+
# @param current_depth [Integer] The current depth in the hash traversal.
|
26
|
+
# @param max_depth [Integer, nil] The maximum depth to traverse. If nil, traverses the entire hash.
|
27
|
+
# @return [Hash] The flattened representation of leaf nodes along with their paths.
|
28
|
+
def leaves(hash, path: [], current_depth: 0, max_depth: nil)
|
29
|
+
hash.each_with_object({}) do |(key, value), result|
|
30
|
+
current_path = path + [key]
|
31
|
+
|
32
|
+
if value.is_a?(Hash) && (max_depth.nil? || current_depth < max_depth)
|
33
|
+
result.merge!(leaves(value, path: current_path, current_depth: current_depth + 1, max_depth: max_depth))
|
34
|
+
else
|
35
|
+
result[current_path] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# rubocop:disable Metrics/AbcSize
|
41
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
43
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
44
|
+
|
45
|
+
# Defines behavior for storing deeply nested data in a database column.
|
46
|
+
#
|
47
|
+
# @param accessor_name [Symbol, String] The name of the accessor for the deep store.
|
48
|
+
# @param payload [Hash] The hash representing the deeply nested data.
|
49
|
+
# @param suffix [Boolean] Whether to include a suffix in the accessor name.
|
50
|
+
# @param column_required [Boolean] Whether the corresponding column is required in the database table.
|
51
|
+
# @raise [ActiveRecord::Deepstore::Error] If the deep store is already declared.
|
52
|
+
# @raise [NotImplementedError] If the required column is not found in the database table.
|
53
|
+
# @return [void]
|
54
|
+
def deep_store(accessor_name, payload, suffix: true, column_required: true)
|
55
|
+
accessor_name = accessor_name.to_s.parameterize.underscore
|
56
|
+
|
57
|
+
raise Error, "Deep store '#{accessor_name}' is already declared" if deep_stores.include?(accessor_name)
|
58
|
+
|
59
|
+
@deep_stores << accessor_name
|
60
|
+
|
61
|
+
if column_required && (columns.find do |c|
|
62
|
+
c.name == accessor_name.to_s
|
63
|
+
end).blank?
|
64
|
+
raise NotImplementedError,
|
65
|
+
"Column #{accessor_name} not found for table #{table_name}"
|
66
|
+
end
|
67
|
+
|
68
|
+
serialize accessor_name, type: Hash, default: payload, yaml: { unsafe_load: true } if payload.is_a?(Hash)
|
69
|
+
|
70
|
+
define_method(:"default_#{accessor_name}") { payload.try(:with_indifferent_access) || payload }
|
71
|
+
|
72
|
+
define_method(:"reset_#{accessor_name}") { assign_attributes(accessor_name => send(:"default_#{accessor_name}")) }
|
73
|
+
|
74
|
+
define_method(:"reset_#{accessor_name}!") { update(accessor_name => send(:"default_#{accessor_name}")) }
|
75
|
+
|
76
|
+
define_method(:"#{accessor_name}_changes") do
|
77
|
+
old_value = send(:"#{accessor_name}_was")
|
78
|
+
current_value = send(accessor_name)
|
79
|
+
old_value == current_value ? {} : { old_value => current_value }
|
80
|
+
end
|
81
|
+
|
82
|
+
define_method(:"#{accessor_name}=") do |value|
|
83
|
+
old_value = send(:"#{accessor_name}_was")
|
84
|
+
|
85
|
+
if value.is_a?(Hash)
|
86
|
+
value = {}.with_indifferent_access if value.blank?
|
87
|
+
self.class.leaves(value).each do |leaf_path, leaf_value|
|
88
|
+
default_value = leaf_path.inject(payload.with_indifferent_access) do |h, key|
|
89
|
+
h.is_a?(Hash) ? h.fetch(key, h) : h
|
90
|
+
end
|
91
|
+
cast_type = self.class.cast_type_from_name(self.class.cast_type_name_from_value(default_value))
|
92
|
+
|
93
|
+
# Traverse the hash using the leaf path and update the leaf value.
|
94
|
+
leaf_key = leaf_path.pop
|
95
|
+
parent_hash = leaf_path.inject(value, :[])
|
96
|
+
# old_leaf_value = parent_hash[leaf_key]
|
97
|
+
new_leaf_value = cast_type.cast(leaf_value)
|
98
|
+
old_parent_hash = parent_hash.dup
|
99
|
+
parent_hash[leaf_key] = new_leaf_value
|
100
|
+
|
101
|
+
instance_variable_set(:"@#{leaf_path.join("_")}_#{accessor_name}_was",
|
102
|
+
old_parent_hash.with_indifferent_access)
|
103
|
+
end
|
104
|
+
|
105
|
+
formatted_value = payload.with_indifferent_access.deep_merge(value)
|
106
|
+
else
|
107
|
+
default_value = send(:"default_#{accessor_name}")
|
108
|
+
cast_type = self.class.cast_type_from_name(self.class.cast_type_name_from_value(default_value))
|
109
|
+
formatted_value = cast_type.cast(value)
|
110
|
+
end
|
111
|
+
|
112
|
+
instance_variable_set(:"@#{accessor_name}_was", old_value)
|
113
|
+
|
114
|
+
super(formatted_value)
|
115
|
+
end
|
116
|
+
# rubocop:enable Metrics/AbcSize
|
117
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
118
|
+
# rubocop:enable Metrics/MethodLength
|
119
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
120
|
+
|
121
|
+
return unless payload.is_a?(Hash)
|
122
|
+
|
123
|
+
payload.each do |key, value|
|
124
|
+
deep_store_accessor(accessor_name, payload, key, value, suffix)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Reloads the model instance and clears deep store changes information.
|
129
|
+
#
|
130
|
+
# @param args [Array] Arguments to pass to the reload method.
|
131
|
+
# @return [void]
|
132
|
+
define_method(:reload) do |*args|
|
133
|
+
clear_deep_store_changes_information
|
134
|
+
super(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Clears deep store changes information.
|
138
|
+
#
|
139
|
+
# @return [void]
|
140
|
+
define_method(:clear_deep_store_changes_information) do
|
141
|
+
self.class.deep_stored_accessors.each do |accessor|
|
142
|
+
formatted_accessor = accessor.to_s.parameterize.underscore
|
143
|
+
instance_variable_set(:"@#{formatted_accessor}_was", send(formatted_accessor))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Defines accessor methods for nested keys within the deep store hash.
|
148
|
+
#
|
149
|
+
# @param accessor_name [Symbol, String] The name of the deep store accessor.
|
150
|
+
# @param payload [Hash] The hash representing the deeply nested data.
|
151
|
+
# @param key [Symbol, String] The key within the hash.
|
152
|
+
# @param value [Object] The value associated with the key.
|
153
|
+
# @param suffix [Boolean] Whether to include a suffix in the accessor name.
|
154
|
+
# @return [void]
|
155
|
+
def deep_store_accessor(accessor_name, payload, key, value, suffix)
|
156
|
+
store_json_accessor(accessor_name, payload, key, suffix)
|
157
|
+
|
158
|
+
deep_store(deep_accessor_name(accessor_name, key), value, column_required: false)
|
159
|
+
|
160
|
+
return if value.is_a?(Hash)
|
161
|
+
|
162
|
+
define_method(deep_accessor_name(accessor_name, key)) do
|
163
|
+
return value unless (hash = public_send(accessor_name)).is_a?(Hash) && hash.key?(key)
|
164
|
+
|
165
|
+
hash[key]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# rubocop:disable Metrics/MethodLength
|
170
|
+
|
171
|
+
# Defines accessor methods for individual keys within the nested hash.
|
172
|
+
#
|
173
|
+
# @param accessor_name [Symbol, String] The name of the deep store accessor.
|
174
|
+
# @param hash [Hash] The hash representing the deeply nested data.
|
175
|
+
# @param key [Symbol, String] The key within the hash.
|
176
|
+
# @param suffix [Boolean] Whether to include a suffix in the accessor name.
|
177
|
+
# @return [void]
|
178
|
+
def store_json_accessor(accessor_name, hash, key, suffix)
|
179
|
+
store_accessor(accessor_name.to_sym, key, suffix: suffix)
|
180
|
+
base_method_name = deep_accessor_name(accessor_name, key)
|
181
|
+
@deep_stored_accessors ||= []
|
182
|
+
@deep_stored_accessors << base_method_name
|
183
|
+
attribute base_method_name, cast_type_name_from_value(hash[key])
|
184
|
+
|
185
|
+
define_method(:"#{base_method_name}_was") do
|
186
|
+
method_name = :"#{base_method_name}_was"
|
187
|
+
return instance_variable_get("@#{method_name}") if instance_variable_defined?("@#{method_name}")
|
188
|
+
|
189
|
+
instance_variable_set("@#{method_name}", send(base_method_name))
|
190
|
+
end
|
191
|
+
|
192
|
+
define_method(:"#{base_method_name}_changed?") do
|
193
|
+
send(:"#{base_method_name}_changes").any?
|
194
|
+
end
|
195
|
+
end
|
196
|
+
# rubocop:enable Metrics/MethodLength
|
197
|
+
|
198
|
+
# Generates a unique name for accessor methods based on the accessor name and key.
|
199
|
+
#
|
200
|
+
# @param accessor_name [Symbol, String] The name of the deep store accessor.
|
201
|
+
# @param key [Symbol, String] The key within the hash.
|
202
|
+
# @return [String] The generated accessor name.
|
203
|
+
def deep_accessor_name(accessor_name, key)
|
204
|
+
"#{key.to_s.parameterize.underscore}_#{accessor_name.to_s.parameterize.underscore}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# Determines the data type for serialization based on the value type.
|
208
|
+
#
|
209
|
+
# @param name [Symbol, String] The name of the data type.
|
210
|
+
# @return [ActiveRecord::Type::Value] The corresponding data type.
|
211
|
+
def cast_type_from_name(name)
|
212
|
+
ActiveRecord::Type.lookup name.to_sym, adapter: ActiveRecord::Type.adapter_name_from(self)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Determines the data type name based on the value.
|
216
|
+
#
|
217
|
+
# @param value [Object] The value for which to determine the data type name.
|
218
|
+
# @return [Symbol] The name of the data type.
|
219
|
+
def cast_type_name_from_value(value)
|
220
|
+
type_mappings = {
|
221
|
+
TrueClass => :boolean,
|
222
|
+
FalseClass => :boolean,
|
223
|
+
NilClass => :string,
|
224
|
+
Hash => :text
|
225
|
+
}
|
226
|
+
|
227
|
+
type_mappings.fetch(value.class, value.class.name.underscore.to_sym)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-deepstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emmanuel Cousin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-04-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.0'
|
41
|
+
description: ActiveRecord-Deepstore enhances ActiveRecord models with powerful functionality
|
42
|
+
for handling deeply nested data structures within a database column. It provides
|
43
|
+
methods for storing, accessing, and managing deeply nested data, making it easier
|
44
|
+
to work with complex data structures in your Rails applications. With ActiveRecord-Deepstore,
|
45
|
+
you can seamlessly store nested hashes in database columns, access nested data with
|
46
|
+
simple method calls, track changes to nested attributes, and much more. This gem
|
47
|
+
simplifies the handling of complex data structures, improving the maintainability
|
48
|
+
and readability of your Rails codebase.
|
49
|
+
email:
|
50
|
+
- emmanuel@hey.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- ".rubocop.yml"
|
56
|
+
- CHANGELOG.md
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- activerecord-deepstore.gemspec
|
61
|
+
- activerecord-deepstore.sqlite3
|
62
|
+
- config/database.yml
|
63
|
+
- db/activerecord-deepstore.sqlite3
|
64
|
+
- lib/active_record/deepstore.rb
|
65
|
+
- lib/active_record/deepstore/version.rb
|
66
|
+
- sig/activerecord/deepstore.rbs
|
67
|
+
homepage: https://github.com/EmCousin/activerecord-deepstore
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata:
|
71
|
+
allowed_push_host: https://rubygems.org
|
72
|
+
homepage_uri: https://github.com/EmCousin/activerecord-deepstore
|
73
|
+
source_code_uri: https://github.com/EmCousin/activerecord-deepstore
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.6.0
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubygems_version: 3.4.21
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: ActiveRecord-Deepstore adds powerful functionality to ActiveRecord models
|
93
|
+
for handling deeply nested data structures within database columns. Simplify storing,
|
94
|
+
accessing, and managing complex nested data in your Rails applications with ease.
|
95
|
+
test_files: []
|