activemodel_object_info 0.4.0 → 0.4.1
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/.rubocop.yml +1 -0
- data/CHANGELOG +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +33 -3
- data/README.zh-CN.md +34 -4
- data/activemodel_object_info.gemspec +1 -1
- data/lib/activemodel_object_info/base.rb +48 -3
- data/lib/activemodel_object_info/deleted_operation.rb +40 -0
- data/lib/activemodel_object_info/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0aebeaaaa8f81c3ee9b2107286f9cfa30ebbd6981ae8da0284bd71537f51a75
|
|
4
|
+
data.tar.gz: 042ee4576749af306512a90eee1cb3c50c3cacbb786204c9ae6af3a7f5cbd6e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 401ebe7b53349260c17f509a4d61391f67880da739ed55ef386af77732d85341a5b27d59e905aed27b70990745a5608b45ed62e231965fe87e16caeae16fcc2b
|
|
7
|
+
data.tar.gz: bc40adcb1e297058f46b5c172cf28b68ebdab7693dfd57bead07bd9593509cf6c7372c99b9fcb3af12ace198880e92da737116d117e1345645e6dfb7e0cb1884
|
data/.rubocop.yml
CHANGED
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
|
|
4
|
+
## [0.4.1] - 2026-07-28
|
|
5
|
+
### Added
|
|
6
|
+
- **Base**: Added support for nested output of associated objects via `includes` option.
|
|
7
|
+
- **Base**: Added support for contextual formats via `context` parameter and `INSTANCE_INFO_#{CONTEXT}` constants.
|
|
8
|
+
- **DeletedOperation**: Added `restore` and `restore!` methods to undelete soft-deleted records.
|
|
9
|
+
|
|
4
10
|
## [0.4.0] - 2026-07-28
|
|
5
11
|
### Fixed
|
|
6
12
|
- Fixed Ruby 3.x keyword arguments compatibility issue for `instance_info` method, while keeping 100% backward compatibility for existing usages.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
Add this line to your application's Gemfile:
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
|
-
gem 'activemodel-object-info', '~> 0.4.
|
|
36
|
+
gem 'activemodel-object-info', '~> 0.4.1'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
And then execute:
|
|
@@ -65,6 +65,11 @@ class User < ApplicationRecord
|
|
|
65
65
|
{ name: :virtual_field, type: :abstract, filter: ->(*) { "#{id}-#{name}" } } # Virtual field
|
|
66
66
|
]
|
|
67
67
|
}.freeze
|
|
68
|
+
|
|
69
|
+
# Define contextual output configuration (Must be named INSTANCE_INFO_{CONTEXT})
|
|
70
|
+
INSTANCE_INFO_DETAIL = {
|
|
71
|
+
only: [:id, :name, :email, :phone]
|
|
72
|
+
}.freeze
|
|
68
73
|
end
|
|
69
74
|
```
|
|
70
75
|
|
|
@@ -73,13 +78,31 @@ end
|
|
|
73
78
|
```ruby
|
|
74
79
|
user = User.first
|
|
75
80
|
|
|
76
|
-
# Use the default INSTANCE_INFO constant
|
|
81
|
+
# 1. Use the default INSTANCE_INFO constant
|
|
77
82
|
user.instance_info
|
|
78
83
|
# => { id: 1, user_name: "John", status: "Active", created_at: "2026-07-28", virtual_field: "1-John" }
|
|
79
84
|
|
|
80
|
-
#
|
|
85
|
+
# 2. Contextual output: Pass `context` to automatically use the corresponding INSTANCE_INFO_{CONTEXT} constant
|
|
86
|
+
user.instance_info(context: :detail)
|
|
87
|
+
# => { id: 1, name: "John", email: "a@b.com", phone: "12345" }
|
|
88
|
+
|
|
89
|
+
# 3. Override at runtime (Supports Ruby 2.x Hash and Ruby 3.x Keyword Arguments)
|
|
81
90
|
user.instance_info(only: [:id, :name])
|
|
82
91
|
# => { id: 1, name: "John" }
|
|
92
|
+
|
|
93
|
+
# 4. Nested output for Associations (includes)
|
|
94
|
+
user.instance_info(
|
|
95
|
+
only: [:id, :name],
|
|
96
|
+
includes: {
|
|
97
|
+
profile: { only: [:avatar_url, :bio] }, # Single instance (has_one / belongs_to)
|
|
98
|
+
roles: { only: [:role_name] } # Collection (has_many)
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
# => {
|
|
102
|
+
# id: 1, name: "John",
|
|
103
|
+
# profile: { avatar_url: "...", bio: "..." },
|
|
104
|
+
# roles: [{ role_name: "admin" }, { role_name: "editor" }]
|
|
105
|
+
# }
|
|
83
106
|
```
|
|
84
107
|
|
|
85
108
|
### 2. Migration Macros (TableDefinition)
|
|
@@ -152,6 +175,13 @@ user.soft_delete(user_id: current_user.id, refresh_updated: true)
|
|
|
152
175
|
|
|
153
176
|
# 4. Strict Soft Delete (raises exception on failure):
|
|
154
177
|
user.soft_delete!(user_id: current_user.id)
|
|
178
|
+
|
|
179
|
+
# 5. Restore soft-deleted record (Undelete):
|
|
180
|
+
# This resets the `deleted` flag to 0 and clears `deleted_by` and `deleted_at`
|
|
181
|
+
user.restore
|
|
182
|
+
|
|
183
|
+
# 6. Restore and refresh updated_at with user_id:
|
|
184
|
+
user.restore(user_id: current_user.id, refresh_updated: true)
|
|
155
185
|
```
|
|
156
186
|
|
|
157
187
|
## Development & Testing
|
data/README.zh-CN.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
将以下代码添加到你项目中的 Gemfile 里:
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
|
-
gem 'activemodel-object-info', '~> 0.4.
|
|
36
|
+
gem 'activemodel-object-info', '~> 0.4.1'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
然后执行依赖安装:
|
|
@@ -54,7 +54,7 @@ $ bundle install
|
|
|
54
54
|
class User < ApplicationRecord
|
|
55
55
|
include ActivemodelObjectInfo::Base
|
|
56
56
|
|
|
57
|
-
#
|
|
57
|
+
# 默认的输出配置选项
|
|
58
58
|
INSTANCE_INFO = {
|
|
59
59
|
only: [:id, :name, :status, :created_at],
|
|
60
60
|
attributes: [
|
|
@@ -65,6 +65,11 @@ class User < ApplicationRecord
|
|
|
65
65
|
{ name: :virtual_field, type: :abstract, filter: ->(*) { "#{id}-#{name}" } } # 根据其他字段合成的虚拟字段
|
|
66
66
|
]
|
|
67
67
|
}.freeze
|
|
68
|
+
|
|
69
|
+
# 自定义场景输出配置(名称格式必须为 INSTANCE_INFO_场景名)
|
|
70
|
+
INSTANCE_INFO_DETAIL = {
|
|
71
|
+
only: [:id, :name, :email, :phone]
|
|
72
|
+
}.freeze
|
|
68
73
|
end
|
|
69
74
|
```
|
|
70
75
|
|
|
@@ -73,13 +78,31 @@ end
|
|
|
73
78
|
```ruby
|
|
74
79
|
user = User.first
|
|
75
80
|
|
|
76
|
-
# 不传参时,默认使用模型中的 INSTANCE_INFO 常量配置
|
|
81
|
+
# 1. 不传参时,默认使用模型中的 INSTANCE_INFO 常量配置
|
|
77
82
|
user.instance_info
|
|
78
83
|
# => { id: 1, user_name: "John", status: "活跃", created_at: "2026-07-28", virtual_field: "1-John" }
|
|
79
84
|
|
|
80
|
-
#
|
|
85
|
+
# 2. 场景化输出:通过 context 参数指定场景,自动读取对应的 INSTANCE_INFO_DETAIL 常量
|
|
86
|
+
user.instance_info(context: :detail)
|
|
87
|
+
# => { id: 1, name: "John", email: "a@b.com", phone: "12345" }
|
|
88
|
+
|
|
89
|
+
# 3. 运行时覆盖配置(完美兼容 Ruby 2.x 的 Hash 传参和 Ruby 3.x 的关键字参数)
|
|
81
90
|
user.instance_info(only: [:id, :name])
|
|
82
91
|
# => { id: 1, name: "John" }
|
|
92
|
+
|
|
93
|
+
# 4. 嵌套输出关联对象 (Associations)
|
|
94
|
+
user.instance_info(
|
|
95
|
+
only: [:id, :name],
|
|
96
|
+
includes: {
|
|
97
|
+
profile: { only: [:avatar_url, :bio] }, # has_one / belongs_to 单实例
|
|
98
|
+
roles: { only: [:role_name] } # has_many 集合实例
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
# => {
|
|
102
|
+
# id: 1, name: "John",
|
|
103
|
+
# profile: { avatar_url: "...", bio: "..." },
|
|
104
|
+
# roles: [{ role_name: "admin" }, { role_name: "editor" }]
|
|
105
|
+
# }
|
|
83
106
|
```
|
|
84
107
|
|
|
85
108
|
### 2. 数据库迁移宏 (TableDefinition)
|
|
@@ -150,6 +173,13 @@ user.soft_delete(user_id: current_user.id, refresh_updated: true)
|
|
|
150
173
|
|
|
151
174
|
# 4. 强校验软删除 (底层调用 save!,失败时抛出异常):
|
|
152
175
|
user.soft_delete!(user_id: current_user.id)
|
|
176
|
+
|
|
177
|
+
# 5. 恢复已软删除的数据 (撤销软删除):
|
|
178
|
+
# 此操作会将 deleted 标记重置为 0,并清空 deleted_by 和 deleted_at
|
|
179
|
+
user.restore
|
|
180
|
+
|
|
181
|
+
# 6. 恢复并同时记录更新人与更新时间:
|
|
182
|
+
user.restore(user_id: current_user.id, refresh_updated: true)
|
|
153
183
|
```
|
|
154
184
|
|
|
155
185
|
## 开发与测试
|
|
@@ -32,6 +32,7 @@ module ActivemodelObjectInfo
|
|
|
32
32
|
# @author shiner527 <shiner527@hotmail.com>
|
|
33
33
|
#
|
|
34
34
|
# [Changelog]
|
|
35
|
+
# [2026-07-28] 支持关联对象嵌套输出 (includes) 与场景化输出配置 (context) (shiner527)
|
|
35
36
|
# [2026-07-28] 补充完整 YARD 文档及核心注释,重构 instance_info 签名以兼容 Ruby 3.x 关键字参数 (shiner527)
|
|
36
37
|
# [2021-04-19] 创建基础模块,提供 instance_info 数据格式化输出功能 (shiner527)
|
|
37
38
|
#
|
|
@@ -56,11 +57,13 @@ module ActivemodelObjectInfo
|
|
|
56
57
|
#
|
|
57
58
|
# @param [Hash] options_hash 设置选项(传统位置参数 Hash),如果传入将被与 keyword_options 合并
|
|
58
59
|
# @param [Hash] keyword_options 设置选项(Ruby 3 关键字参数)
|
|
60
|
+
# @option keyword_options [Symbol, String] :context 场景上下文名称。如果提供,将优先读取 "INSTANCE_INFO_#{context.upcase}" 常量。
|
|
59
61
|
# @option keyword_options [Array<Symbol, Hash>] :attributes 具体每一项输出的设置数组。每个元素既可以是标识符实例也可以是一个散列实例。
|
|
60
62
|
# 如果是标识符实例,则表示输出该属性。如果是一个散列实例,则按照散列中的设定值去生成对应的内容。
|
|
61
63
|
# @option keyword_options [Array<Symbol>] :only 给出具体可以用来输出的字段属性名数组。
|
|
62
64
|
# @option keyword_options [Array<Symbol>] :except 给出需要被排除输出的字段属性名数组。
|
|
63
65
|
# @option keyword_options [String, Symbol] :datetime_format 全局的时间格式设置。
|
|
66
|
+
# @option keyword_options [Hash] :includes 需要嵌套输出的关联对象配置(如 { profile: { only: [:avatar] } })。
|
|
64
67
|
#
|
|
65
68
|
# @return [Hash] 返回的处理过的该对象的信息散列。
|
|
66
69
|
#
|
|
@@ -68,8 +71,18 @@ module ActivemodelObjectInfo
|
|
|
68
71
|
# 合并传统参数与关键字参数,完美兼容 Ruby 2.x 的 Hash 传参和 Ruby 3.x 的 **kwargs 传参
|
|
69
72
|
options = (options_hash || {}).merge(keyword_options)
|
|
70
73
|
|
|
71
|
-
#
|
|
72
|
-
options
|
|
74
|
+
# 尝试获取当前类上配置的常量作为默认选项
|
|
75
|
+
# 优先级:传入的参数 options > 场景常量 INSTANCE_INFO_#{CONTEXT} > 默认常量 INSTANCE_INFO
|
|
76
|
+
if options.blank? || (options.keys.map(&:to_sym) == [:context] && options[:context].present?)
|
|
77
|
+
context_name = options[:context].to_s.upcase
|
|
78
|
+
|
|
79
|
+
# 尝试寻找带场景的常量
|
|
80
|
+
context_constant_name = "#{self.class}::INSTANCE_INFO_#{context_name}"
|
|
81
|
+
options = context_constant_name.safe_constantize if context_name.present?
|
|
82
|
+
|
|
83
|
+
# 如果带场景的常量不存在,或者没传场景,降级寻找默认常量
|
|
84
|
+
options ||= "#{self.class}::INSTANCE_INFO".safe_constantize || {}
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
# 将 options 的 key 转为 symbol,避免传入字符串 key 导致匹配不到
|
|
75
88
|
options = options.deep_symbolize_keys
|
|
@@ -159,8 +172,40 @@ module ActivemodelObjectInfo
|
|
|
159
172
|
end
|
|
160
173
|
end
|
|
161
174
|
|
|
162
|
-
# 统一将结果第一层的键名转化为 Symbol
|
|
175
|
+
# 统一将结果第一层的键名转化为 Symbol 格式
|
|
163
176
|
result.symbolize_keys!
|
|
177
|
+
|
|
178
|
+
# 5. 处理嵌套关联对象 (includes)
|
|
179
|
+
format_associations!(result, options)
|
|
180
|
+
|
|
181
|
+
result
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
# 处理并格式化嵌套关联对象
|
|
187
|
+
# @param [Hash] result 当前实例的输出结果散列
|
|
188
|
+
# @param [Hash] options 包含 :includes 配置的选项散列
|
|
189
|
+
def format_associations!(result, options)
|
|
190
|
+
return unless options[:includes].present? && options[:includes].is_a?(::Hash)
|
|
191
|
+
|
|
192
|
+
options[:includes].each do |association_name, assoc_options|
|
|
193
|
+
assoc_options = {} unless assoc_options.is_a?(::Hash)
|
|
194
|
+
|
|
195
|
+
# 安全获取关联对象
|
|
196
|
+
next unless respond_to?(association_name)
|
|
197
|
+
|
|
198
|
+
assoc_obj = __send__(association_name)
|
|
199
|
+
|
|
200
|
+
# 遍历处理关联对象
|
|
201
|
+
if assoc_obj.respond_to?(:map)
|
|
202
|
+
result[association_name.to_sym] = assoc_obj.map do |item|
|
|
203
|
+
item.respond_to?(:instance_info) ? item.instance_info(**assoc_options) : item
|
|
204
|
+
end
|
|
205
|
+
elsif assoc_obj.present?
|
|
206
|
+
result[association_name.to_sym] = assoc_obj.respond_to?(:instance_info) ? assoc_obj.instance_info(**assoc_options) : assoc_obj
|
|
207
|
+
end
|
|
208
|
+
end
|
|
164
209
|
end
|
|
165
210
|
end
|
|
166
211
|
end
|
|
@@ -30,11 +30,14 @@ module ActivemodelObjectInfo
|
|
|
30
30
|
# 核心重要方法:
|
|
31
31
|
# - {#soft_delete}:执行软删除并调用 save 更新数据。
|
|
32
32
|
# - {#soft_delete!}:执行软删除并调用 save! 更新数据(抛出异常)。
|
|
33
|
+
# - {#restore}:恢复已软删除的数据。
|
|
34
|
+
# - {#restore!}:强制恢复已软删除的数据(抛出异常)。
|
|
33
35
|
# - {#delete_block}:底层通用的删除字段赋值逻辑块。
|
|
34
36
|
#
|
|
35
37
|
# @author shiner527 <shiner527@hotmail.com>
|
|
36
38
|
#
|
|
37
39
|
# [Changelog]
|
|
40
|
+
# [2026-07-28] 新增 restore 与 restore! 方法以支持软删除数据恢复 (shiner527)
|
|
38
41
|
# [2026-07-28] 补充完整 YARD 文档、行内注释,清理尾随空格 (shiner527)
|
|
39
42
|
# [2021-04-19] 创建基础模块,提供软删除与作用域注入功能 (shiner527)
|
|
40
43
|
#
|
|
@@ -97,6 +100,43 @@ module ActivemodelObjectInfo
|
|
|
97
100
|
|
|
98
101
|
save!(touch: opts[:refresh_updated])
|
|
99
102
|
end
|
|
103
|
+
|
|
104
|
+
# 6. 底层的恢复逻辑块 (给实例对象的内存属性赋值,清除删除标记)
|
|
105
|
+
define_method(:restore_block) do |**options|
|
|
106
|
+
options[:refresh_updated] = options[:refresh_updated].nil? ? false : options[:refresh_updated]
|
|
107
|
+
|
|
108
|
+
updated_by_field = (options[:updated_by_field] || 'updated_by').to_sym
|
|
109
|
+
deleted_by_field = (options[:deleted_by_field] || 'deleted_by').to_sym
|
|
110
|
+
deleted_at_field = (options[:deleted_at_field] || 'deleted_at').to_sym
|
|
111
|
+
|
|
112
|
+
# 核心:恢复状态为有效标记值
|
|
113
|
+
__send__("#{deleted_field}=", deleted_value_valid)
|
|
114
|
+
|
|
115
|
+
# 审计:清除删除人、删除时间,根据配置选择性地设置恢复操作的更新人
|
|
116
|
+
__send__("#{deleted_by_field}=", nil) if respond_to?(deleted_by_field)
|
|
117
|
+
__send__("#{deleted_at_field}=", nil) if respond_to?(deleted_at_field)
|
|
118
|
+
__send__("#{updated_by_field}=", options[:user_id]) if respond_to?(updated_by_field) && options[:user_id].present? && options[:refresh_updated]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# 7. 暴露的常规软删除恢复方法
|
|
122
|
+
define_method(:restore) do |**options|
|
|
123
|
+
opts = options.deep_symbolize_keys
|
|
124
|
+
return unless respond_to?(deleted_field)
|
|
125
|
+
|
|
126
|
+
restore_block(**opts)
|
|
127
|
+
|
|
128
|
+
save(touch: opts[:refresh_updated])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# 8. 暴露的强校验软删除恢复方法
|
|
132
|
+
define_method(:restore!) do |**options|
|
|
133
|
+
opts = options.deep_symbolize_keys
|
|
134
|
+
return unless respond_to?(deleted_field)
|
|
135
|
+
|
|
136
|
+
restore_block(**opts)
|
|
137
|
+
|
|
138
|
+
save!(touch: opts[:refresh_updated])
|
|
139
|
+
end
|
|
100
140
|
end
|
|
101
141
|
end
|
|
102
142
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activemodel_object_info
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- shiner
|
|
@@ -170,7 +170,7 @@ homepage: https://github.com/shiner527/activemodel-object-info
|
|
|
170
170
|
licenses:
|
|
171
171
|
- MIT
|
|
172
172
|
metadata:
|
|
173
|
-
rubygems_mfa_required: '
|
|
173
|
+
rubygems_mfa_required: 'true'
|
|
174
174
|
post_install_message:
|
|
175
175
|
rdoc_options: []
|
|
176
176
|
require_paths:
|