activemodel_object_info 0.4.1 → 0.4.2
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/Gemfile.lock +1 -1
- data/README.md +36 -8
- data/README.zh-CN.md +35 -7
- data/lib/activemodel_object_info/base.rb +106 -17
- data/lib/activemodel_object_info/deleted_operation.rb +13 -3
- 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: 1096a56e9db6886e83460c0015079b2a583e940744d2be9b5bc2a2d6461943aa
|
|
4
|
+
data.tar.gz: c91020d37e00fad63a389db8a2246b7358ba814d303a08466614c34c0dad5c34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e17eda2eab30babf92e4f802329f048a1f40ad2e2ea4e226a1671543243315262104a9f806569b032a106c9473c2d1f919017f52b667b32f9901fd25b4510ff
|
|
7
|
+
data.tar.gz: 54f2f7c502906709755d31cb7346bf195f3fba28bd9f0408b8c4f3049b0007bf9eea7c618f461621b9bdaccd2c31d23390fca8f9b3296566093c1c727ca865de
|
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.2'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
And then execute:
|
|
@@ -90,7 +90,13 @@ user.instance_info(context: :detail)
|
|
|
90
90
|
user.instance_info(only: [:id, :name])
|
|
91
91
|
# => { id: 1, name: "John" }
|
|
92
92
|
|
|
93
|
-
# 4.
|
|
93
|
+
# 4. Exclude specific fields (except)
|
|
94
|
+
# By default, instance_info filters out deleted, deleted_by, and deleted_at.
|
|
95
|
+
# You can override this behavior by passing an except array:
|
|
96
|
+
user.instance_info(except: [:status, :created_at])
|
|
97
|
+
# => { id: 1, user_name: "John", virtual_field: "1-John" }
|
|
98
|
+
|
|
99
|
+
# 5. Nested output for Associations (includes)
|
|
94
100
|
user.instance_info(
|
|
95
101
|
only: [:id, :name],
|
|
96
102
|
includes: {
|
|
@@ -105,6 +111,18 @@ user.instance_info(
|
|
|
105
111
|
# }
|
|
106
112
|
```
|
|
107
113
|
|
|
114
|
+
**Formatting Options:**
|
|
115
|
+
For `Date`, `Time`, and `DateTime` fields, multiple formatting strategies are supported:
|
|
116
|
+
- `format: :standard`: Keeps the original object, does not convert to string
|
|
117
|
+
- `format: :full` / `:min` / `:date` / `:month` / `:year`: Uses built-in shortcut formats (e.g. `:date` outputs `%Y-%m-%d`)
|
|
118
|
+
- `format: '%Y/%m/%d'`: Original strftime string
|
|
119
|
+
- `format: ->(v) { "#{v.year}-#{v.month}-#{v.day}" }`: Pass a `Proc` / `Lambda` for completely custom formatting (supported since `0.4.2`)
|
|
120
|
+
|
|
121
|
+
*Global Date Formatting*: In addition to specifying `format` for individual fields in the `attributes` array, you can set a global default by passing the `datetime_format` parameter:
|
|
122
|
+
```ruby
|
|
123
|
+
user.instance_info(datetime_format: :date) # All time fields will use the :date format by default
|
|
124
|
+
```
|
|
125
|
+
|
|
108
126
|
### 2. Migration Macros (TableDefinition)
|
|
109
127
|
|
|
110
128
|
The `ActivemodelObjectInfo::TableDefinition` module extends ActiveRecord's migration capabilities to generate audit fields effortlessly.
|
|
@@ -120,16 +138,20 @@ class CreateUsers < ActiveRecord::Migration[6.1]
|
|
|
120
138
|
create_table :users do |t|
|
|
121
139
|
t.string :name
|
|
122
140
|
|
|
123
|
-
# Generates full audit suite:
|
|
124
|
-
#
|
|
141
|
+
# 1. Generates full audit suite:
|
|
142
|
+
# If no arguments are passed, it generates fields for 'created', 'updated', and 'deleted' operations.
|
|
143
|
+
# Generates: created_by, created_at, updated_by, updated_at, deleted(int), deleted_by, deleted_at
|
|
144
|
+
# Note: The generated timestamp and operator fields are indexed by default (index: true).
|
|
125
145
|
t.generate_operations
|
|
126
146
|
|
|
127
|
-
# Or generate specific custom audit fields:
|
|
147
|
+
# 2. Or generate specific custom audit fields:
|
|
128
148
|
# Generates: audit_by (bigint), audit_at (datetime)
|
|
129
149
|
t.operation_columns(:audit)
|
|
130
150
|
|
|
151
|
+
# 3. Highly customized prefixes, suffixes, and column generation control:
|
|
131
152
|
# Generates: my_review_user (bigint), my_review_time (datetime)
|
|
132
|
-
|
|
153
|
+
# with_operator / with_timestamp can be used to control whether the operator or timestamp fields are generated.
|
|
154
|
+
t.operation_columns(:review, operator_prefix: 'my_', operator_suffix: '_user', timestamp_suffix: '_time', with_operator: true, with_timestamp: true)
|
|
133
155
|
end
|
|
134
156
|
end
|
|
135
157
|
end
|
|
@@ -177,11 +199,17 @@ user.soft_delete(user_id: current_user.id, refresh_updated: true)
|
|
|
177
199
|
user.soft_delete!(user_id: current_user.id)
|
|
178
200
|
|
|
179
201
|
# 5. Restore soft-deleted record (Undelete):
|
|
202
|
+
# Note: Deleted records are hidden by default_scope, so you must use unscoped to find them
|
|
203
|
+
deleted_user = User.unscoped.find(1)
|
|
204
|
+
|
|
180
205
|
# This resets the `deleted` flag to 0 and clears `deleted_by` and `deleted_at`
|
|
181
|
-
|
|
206
|
+
deleted_user.restore
|
|
182
207
|
|
|
183
208
|
# 6. Restore and refresh updated_at with user_id:
|
|
184
|
-
|
|
209
|
+
deleted_user.restore(user_id: current_user.id, refresh_updated: true)
|
|
210
|
+
|
|
211
|
+
# 7. Strict Restore (calls save! and raises exception on failure):
|
|
212
|
+
deleted_user.restore!(user_id: current_user.id)
|
|
185
213
|
```
|
|
186
214
|
|
|
187
215
|
## 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.2'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
然后执行依赖安装:
|
|
@@ -90,7 +90,13 @@ user.instance_info(context: :detail)
|
|
|
90
90
|
user.instance_info(only: [:id, :name])
|
|
91
91
|
# => { id: 1, name: "John" }
|
|
92
92
|
|
|
93
|
-
# 4.
|
|
93
|
+
# 4. 排除输出字段 (except)
|
|
94
|
+
# 默认情况下,instance_info 会自动过滤掉 deleted, deleted_by, deleted_at 字段。
|
|
95
|
+
# 你可以通过传入 except 数组覆盖默认行为,过滤掉你不需要的字段:
|
|
96
|
+
user.instance_info(except: [:status, :created_at])
|
|
97
|
+
# => { id: 1, user_name: "John", virtual_field: "1-John" }
|
|
98
|
+
|
|
99
|
+
# 5. 嵌套输出关联对象 (includes)
|
|
94
100
|
user.instance_info(
|
|
95
101
|
only: [:id, :name],
|
|
96
102
|
includes: {
|
|
@@ -105,6 +111,18 @@ user.instance_info(
|
|
|
105
111
|
# }
|
|
106
112
|
```
|
|
107
113
|
|
|
114
|
+
**支持的时间格式化 (`format`):**
|
|
115
|
+
对于 `Date`、`Time`、`DateTime` 类型的字段,支持以下多种格式化策略:
|
|
116
|
+
- `format: :standard`:保持原生对象,不转字符串
|
|
117
|
+
- `format: :full` / `:min` / `:date` / `:month` / `:year`:使用内置快捷格式,例如 `:date` 会输出 `%Y-%m-%d`
|
|
118
|
+
- `format: '%Y/%m/%d'`:使用原生 strftime 字符串
|
|
119
|
+
- `format: ->(v) { "#{v.year}年#{v.month}月#{v.day}日" }`:传入 `Proc` / `Lambda` 实现完全自定义格式化 (自 `0.4.2` 起支持)
|
|
120
|
+
|
|
121
|
+
*全局时间格式化*:除了在 `attributes` 数组内对单一字段指定 `format` 外,你还可以通过传入 `datetime_format` 参数进行全局默认设定:
|
|
122
|
+
```ruby
|
|
123
|
+
user.instance_info(datetime_format: :date) # 全局的时间默认都会使用 :date 格式
|
|
124
|
+
```
|
|
125
|
+
|
|
108
126
|
### 2. 数据库迁移宏 (TableDefinition)
|
|
109
127
|
|
|
110
128
|
`ActivemodelObjectInfo::TableDefinition` 模块对 ActiveRecord 的 Migration 进行了扩展,让你能够毫不费力地生成审计字段。
|
|
@@ -118,16 +136,20 @@ class CreateUsers < ActiveRecord::Migration[6.1]
|
|
|
118
136
|
t.string :name
|
|
119
137
|
|
|
120
138
|
# 1. 批量生成全套生命周期审计字段:
|
|
139
|
+
# t.generate_operations 不传参时默认生成 created, updated, deleted 三大操作相关字段。
|
|
121
140
|
# 将会自动生成: created_by, created_at, updated_by, updated_at, deleted(int), deleted_by, deleted_at
|
|
141
|
+
# 注意:生成的时间戳和操作人字段默认都是带数据库索引的 (index: true)
|
|
122
142
|
t.generate_operations
|
|
123
143
|
|
|
124
144
|
# 2. 或者生成指定的自定义审计字段:
|
|
145
|
+
# t.operation_columns 会生成 :audit 对应的操作人(bigint)与操作时间(datetime)
|
|
125
146
|
# 将会自动生成: audit_by (bigint), audit_at (datetime)
|
|
126
147
|
t.operation_columns(:audit)
|
|
127
148
|
|
|
128
|
-
# 3.
|
|
149
|
+
# 3. 甚至高度自定义前后缀与控制字段生成:
|
|
129
150
|
# 将会自动生成: my_review_user (bigint), my_review_time (datetime)
|
|
130
|
-
|
|
151
|
+
# with_operator / with_timestamp 可以分别控制是否生成对应的操作人或操作时间字段
|
|
152
|
+
t.operation_columns(:review, operator_prefix: 'my_', operator_suffix: '_user', timestamp_suffix: '_time', with_operator: true, with_timestamp: true)
|
|
131
153
|
end
|
|
132
154
|
end
|
|
133
155
|
end
|
|
@@ -175,11 +197,17 @@ user.soft_delete(user_id: current_user.id, refresh_updated: true)
|
|
|
175
197
|
user.soft_delete!(user_id: current_user.id)
|
|
176
198
|
|
|
177
199
|
# 5. 恢复已软删除的数据 (撤销软删除):
|
|
178
|
-
#
|
|
179
|
-
|
|
200
|
+
# 注意:已被软删除的数据会被 default_scope 过滤,因此需要使用 unscoped 查出
|
|
201
|
+
deleted_user = User.unscoped.find(1)
|
|
202
|
+
|
|
203
|
+
# 执行恢复操作,此操作会将 deleted 标记重置为 0,并清空 deleted_by 和 deleted_at
|
|
204
|
+
deleted_user.restore
|
|
180
205
|
|
|
181
206
|
# 6. 恢复并同时记录更新人与更新时间:
|
|
182
|
-
|
|
207
|
+
deleted_user.restore(user_id: current_user.id, refresh_updated: true)
|
|
208
|
+
|
|
209
|
+
# 7. 强校验恢复 (底层调用 save!,失败时抛出异常):
|
|
210
|
+
deleted_user.restore!(user_id: current_user.id)
|
|
183
211
|
```
|
|
184
212
|
|
|
185
213
|
## 开发与测试
|
|
@@ -32,6 +32,7 @@ module ActivemodelObjectInfo
|
|
|
32
32
|
# @author shiner527 <shiner527@hotmail.com>
|
|
33
33
|
#
|
|
34
34
|
# [Changelog]
|
|
35
|
+
# [2026-07-28] 支持通过 Proc / Lambda 完全自定义时间格式化 (shiner527)
|
|
35
36
|
# [2026-07-28] 支持关联对象嵌套输出 (includes) 与场景化输出配置 (context) (shiner527)
|
|
36
37
|
# [2026-07-28] 补充完整 YARD 文档及核心注释,重构 instance_info 签名以兼容 Ruby 3.x 关键字参数 (shiner527)
|
|
37
38
|
# [2021-04-19] 创建基础模块,提供 instance_info 数据格式化输出功能 (shiner527)
|
|
@@ -53,19 +54,64 @@ module ActivemodelObjectInfo
|
|
|
53
54
|
# ===== 方法定义 =====
|
|
54
55
|
|
|
55
56
|
#
|
|
56
|
-
# 对象信息输出。主要返回给前端一个可用的散列(会被转化为JSON格式)格式的信息并传递给前端。
|
|
57
|
+
# 对象信息输出。主要返回给前端一个可用的散列(会被转化为 JSON 格式)格式的信息并传递给前端。
|
|
58
|
+
#
|
|
59
|
+
# @since 0.1.0 基础功能
|
|
60
|
+
# @since 0.4.0 兼容 Ruby 3.x 关键字参数,支持 :context 与 :includes
|
|
61
|
+
# @since 0.4.2 时间格式化支持 Proc/Lambda 完全自定义
|
|
57
62
|
#
|
|
58
63
|
# @param [Hash] options_hash 设置选项(传统位置参数 Hash),如果传入将被与 keyword_options 合并
|
|
59
64
|
# @param [Hash] keyword_options 设置选项(Ruby 3 关键字参数)
|
|
65
|
+
#
|
|
60
66
|
# @option keyword_options [Symbol, String] :context 场景上下文名称。如果提供,将优先读取 "INSTANCE_INFO_#{context.upcase}" 常量。
|
|
61
67
|
# @option keyword_options [Array<Symbol, Hash>] :attributes 具体每一项输出的设置数组。每个元素既可以是标识符实例也可以是一个散列实例。
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
68
|
+
# 如果是标识符实例,则表示输出该属性。如果是一个散列实例,则按照散列中的设定值去生成对应的内容。
|
|
69
|
+
# 散列中支持如下配置项:
|
|
70
|
+
# - :name (Symbol) 对应的原始字段名或方法名
|
|
71
|
+
# - :as (Symbol) 别名,输出时会使用该别名代替原始字段名
|
|
72
|
+
# - :type (Symbol) 当配置为 :abstract 时表示虚拟字段,不再去对象中调用对应的属性
|
|
73
|
+
# - :filter (Proc, Symbol, Object) 过滤器,如果是 Proc,则通过 instance_exec 执行;若是 Symbol,则调用同名方法;否则直接作为静态值返回
|
|
74
|
+
# - :format (Symbol, String, Proc) 对于时间字段进行格式化。支持预设 Symbol(:full, :min, :date, :month, :year, :standard),原生 strftime 字符串,或通过 Proc 自定义输出
|
|
75
|
+
# @option keyword_options [Array<Symbol>] :only 给出具体可以用来输出的字段属性名数组。只有在 only 中的字段才会被输出。
|
|
76
|
+
# @option keyword_options [Array<Symbol>] :except 给出需要被排除输出的字段属性名数组。默认排除 `deleted` 相关的审计字段。
|
|
77
|
+
# @option keyword_options [String, Symbol] :datetime_format 全局的时间格式设置,作用于所有时间类型但会被字段级的 format 覆盖。
|
|
78
|
+
# @option keyword_options [Hash] :includes 需要嵌套输出的关联对象配置,以 Hash 形式递归定义子对象的输出规则(如 `{ profile: { only: [:avatar] } }`)。
|
|
79
|
+
#
|
|
80
|
+
# @example 基本使用
|
|
81
|
+
# user.instance_info(only: [:id, :name])
|
|
82
|
+
# # => { id: 1, name: "Alice" }
|
|
83
|
+
#
|
|
84
|
+
# @example 复杂字段重命名与过滤
|
|
85
|
+
# user.instance_info(
|
|
86
|
+
# attributes: [
|
|
87
|
+
# :id,
|
|
88
|
+
# { name: :first_name, as: :name },
|
|
89
|
+
# { name: :age, type: :abstract, filter: ->(v) { 2026 - birth_year } }
|
|
90
|
+
# ]
|
|
91
|
+
# )
|
|
92
|
+
# # => { id: 1, name: "Alice", age: 30 }
|
|
93
|
+
#
|
|
94
|
+
# @example 时间自定义格式化
|
|
95
|
+
# user.instance_info(
|
|
96
|
+
# attributes: [
|
|
97
|
+
# { name: :created_at, format: ->(v) { "#{v.year}年#{v.month}月#{v.day}日" } }
|
|
98
|
+
# ]
|
|
99
|
+
# )
|
|
100
|
+
# # => { created_at: "2026年7月28日" }
|
|
101
|
+
#
|
|
102
|
+
# @example 嵌套关联输出
|
|
103
|
+
# user.instance_info(
|
|
104
|
+
# only: [:id, :name],
|
|
105
|
+
# includes: {
|
|
106
|
+
# profile: { only: [:avatar_url] },
|
|
107
|
+
# roles: { only: [:id, :role_name] }
|
|
108
|
+
# }
|
|
109
|
+
# )
|
|
110
|
+
# # => { id: 1, name: "Alice", profile: { avatar_url: "..." }, roles: [{ id: 1, role_name: "admin" }] }
|
|
67
111
|
#
|
|
68
|
-
# @return [Hash]
|
|
112
|
+
# @return [Hash] 返回处理过的该对象的信息散列,键名统一转化为 Symbol。
|
|
113
|
+
#
|
|
114
|
+
# @raise [NoMethodError] 当尝试获取未定义的属性,且未将该属性设为 `type: :abstract` 或配置 `filter` 时可能抛出。
|
|
69
115
|
#
|
|
70
116
|
def instance_info(options_hash = nil, **keyword_options)
|
|
71
117
|
# 合并传统参数与关键字参数,完美兼容 Ruby 2.x 的 Hash 传参和 Ruby 3.x 的 **kwargs 传参
|
|
@@ -74,11 +120,10 @@ module ActivemodelObjectInfo
|
|
|
74
120
|
# 尝试获取当前类上配置的常量作为默认选项
|
|
75
121
|
# 优先级:传入的参数 options > 场景常量 INSTANCE_INFO_#{CONTEXT} > 默认常量 INSTANCE_INFO
|
|
76
122
|
if options.blank? || (options.keys.map(&:to_sym) == [:context] && options[:context].present?)
|
|
77
|
-
context_name = options[:context].to_s.upcase
|
|
123
|
+
context_name = options[:context].to_s.upcase if options[:context].present?
|
|
78
124
|
|
|
79
125
|
# 尝试寻找带场景的常量
|
|
80
|
-
|
|
81
|
-
options = context_constant_name.safe_constantize if context_name.present?
|
|
126
|
+
options = ("#{self.class}::INSTANCE_INFO_#{context_name}".safe_constantize if context_name.present?)
|
|
82
127
|
|
|
83
128
|
# 如果带场景的常量不存在,或者没传场景,降级寻找默认常量
|
|
84
129
|
options ||= "#{self.class}::INSTANCE_INFO".safe_constantize || {}
|
|
@@ -111,9 +156,36 @@ module ActivemodelObjectInfo
|
|
|
111
156
|
# 这里保证了要引入的类中含有 attributes 方法,且为 Hash 类型
|
|
112
157
|
|
|
113
158
|
# 补丁:如果传入了 only_attributes,但是 options[:attributes] 是空(即调用方没传),
|
|
114
|
-
#
|
|
159
|
+
# 并且当前并没有匹配到任何场景常量,我们需要将 only_attributes 中的字段视为基础配置,以确保只包含这几个字段的正常输出
|
|
115
160
|
attribute_configs = only_attributes.map { |attr| { name: attr } } if attribute_configs.empty? && only_attributes.present?
|
|
116
161
|
|
|
162
|
+
# 补丁 2:如果在处理完毕后,attribute_configs 仍然为空,并且 output_attributes 存在
|
|
163
|
+
# 则说明我们正在执行类似 default options 的全量输出或者包含了所有字段的嵌套输出
|
|
164
|
+
# 此时,我们需要基于 output_attributes 自动生成 attribute_configs,但需要保留 options[:attributes] 中已经配置了 format 等逻辑的字段配置
|
|
165
|
+
if attribute_configs.empty? && output_attributes.present?
|
|
166
|
+
# 取出最初预设在 options 里的属性配置
|
|
167
|
+
preset_configs = options[:attributes] || []
|
|
168
|
+
|
|
169
|
+
# 获取 options 中显式定义了的所有字段的原始名和别名
|
|
170
|
+
preset_attr_names = preset_configs.flat_map do |config|
|
|
171
|
+
case config
|
|
172
|
+
when ::Hash
|
|
173
|
+
[config[:name], config[:as]].compact.map(&:to_sym)
|
|
174
|
+
when ::Symbol, ::String
|
|
175
|
+
config.to_sym
|
|
176
|
+
end
|
|
177
|
+
end.compact.uniq
|
|
178
|
+
|
|
179
|
+
supplementary_configs = (output_attributes - preset_attr_names).map { |attr| { name: attr } }
|
|
180
|
+
|
|
181
|
+
# 将预设的规则配置和自动补充的基础配置合并,作为最终的 attribute_configs
|
|
182
|
+
# 注意:为了避免重复处理以及处理顺序问题,我们统一使用预先配置,并在末尾追加没有配置的字段
|
|
183
|
+
attribute_configs = preset_configs + supplementary_configs
|
|
184
|
+
|
|
185
|
+
# 移除 attribute_configs 中的重复项 (如果存在)
|
|
186
|
+
attribute_configs.uniq!
|
|
187
|
+
end
|
|
188
|
+
|
|
117
189
|
attribute_configs.each do |attr_config|
|
|
118
190
|
# 根据配置项的类型(Symbol/String 或 Hash)提取字段名称和具体配置
|
|
119
191
|
if [::String, ::Symbol].any? { |attr_class| attr_config.is_a?(attr_class) }
|
|
@@ -131,13 +203,14 @@ module ActivemodelObjectInfo
|
|
|
131
203
|
raw_name = current_attr_config[:as].present? ? current_attr_config[:as].to_sym : attribute_name
|
|
132
204
|
filter = current_attr_config[:filter]
|
|
133
205
|
|
|
134
|
-
#
|
|
206
|
+
# 获取真正对应的属性的名称和其值
|
|
135
207
|
k = raw_name
|
|
136
|
-
#
|
|
208
|
+
# 当类型为抽象类时,是不会有对应的方法和属性的,因此直接设置为 nil 并期待之后的 filter 等规则来生成实际值。
|
|
137
209
|
v = current_attr_config[:type] == :abstract ? nil : __send__(k)
|
|
138
210
|
|
|
139
211
|
# 跳过条件:既不在最终输出名单中,也不是显式声明的 abstract/method 虚拟字段
|
|
140
|
-
|
|
212
|
+
# 注意:需要同时判断别名(attribute_name)和原名(k)是否在需要输出的列表中
|
|
213
|
+
next unless output_attributes.include?(attribute_name) || output_attributes.include?(k) || %i[abstract method].include?(current_attr_config[:type])
|
|
141
214
|
|
|
142
215
|
# 4. 根据类型或 filter 对值进行格式化转换
|
|
143
216
|
if filter.present?
|
|
@@ -155,6 +228,9 @@ module ActivemodelObjectInfo
|
|
|
155
228
|
attribute_format = current_attr_config[:format].present? ? current_attr_config[:format] : options[:datetime_format]
|
|
156
229
|
result[attribute_name] = if attribute_format.to_s == 'standard'
|
|
157
230
|
v # standard 保持时间对象原生格式
|
|
231
|
+
elsif attribute_format.is_a?(::Proc)
|
|
232
|
+
# 支持传入 Lambda 或 Proc 进行完全自定义的格式化
|
|
233
|
+
instance_exec(v, &attribute_format)
|
|
158
234
|
elsif attribute_format.present?
|
|
159
235
|
# 匹配内置的别名格式 (full/min/date/month/year),否则作为自定义 strftime 字符串处理
|
|
160
236
|
if %i[full min date month year].include?(attribute_format.to_sym)
|
|
@@ -183,9 +259,22 @@ module ActivemodelObjectInfo
|
|
|
183
259
|
|
|
184
260
|
private
|
|
185
261
|
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
262
|
+
#
|
|
263
|
+
# 处理并格式化嵌套关联对象。
|
|
264
|
+
#
|
|
265
|
+
# @api private
|
|
266
|
+
#
|
|
267
|
+
# @note 该方法仅被 `instance_info` 内部调用,用于将配置中 `includes` 的选项应用到实际的关联对象上。
|
|
268
|
+
# 之所以保持私有(private),是因为此方法直接修改了 `instance_info` 输出的 `result` 散列,
|
|
269
|
+
# 其行为和状态与 `instance_info` 的上下文深度绑定,单独对外暴露并无实际意义,反而会破坏对象的封装性。
|
|
270
|
+
#
|
|
271
|
+
# @since 0.4.0
|
|
272
|
+
#
|
|
273
|
+
# @param [Hash] result 当前实例的输出结果散列,关联对象的数据会被注入到该散列中
|
|
274
|
+
# @param [Hash] options 包含 `:includes` 配置的选项散列
|
|
275
|
+
#
|
|
276
|
+
# @return [void]
|
|
277
|
+
#
|
|
189
278
|
def format_associations!(result, options)
|
|
190
279
|
return unless options[:includes].present? && options[:includes].is_a?(::Hash)
|
|
191
280
|
|
|
@@ -20,12 +20,22 @@ module ActivemodelObjectInfo
|
|
|
20
20
|
# DELETED_INVALID_VALUE = true
|
|
21
21
|
# end
|
|
22
22
|
#
|
|
23
|
+
# # 1. 默认查询过滤
|
|
23
24
|
# # 查询数据时会自动应用 default_scope: WHERE is_deleted = false
|
|
24
25
|
# User.all
|
|
25
26
|
#
|
|
26
|
-
# #
|
|
27
|
+
# # 2. 软删除记录
|
|
28
|
+
# # 软删除一条记录(必须要传入 user_id 用于审计)
|
|
27
29
|
# user = User.find(1)
|
|
28
30
|
# user.soft_delete(user_id: current_user.id, refresh_updated: true)
|
|
31
|
+
# # 此时 user 的 is_deleted 变为 true,同时 deleted_at, deleted_by, updated_by 都会被更新
|
|
32
|
+
#
|
|
33
|
+
# # 3. 恢复软删除的数据
|
|
34
|
+
# # 因为已经被软删除的数据会被 default_scope 过滤,所以需要使用 unscoped 查出
|
|
35
|
+
# deleted_user = User.unscoped.find(1)
|
|
36
|
+
# # 执行恢复操作,同样可以传入 user_id 记录是谁执行的恢复,并选择是否刷新 updated_at
|
|
37
|
+
# deleted_user.restore(user_id: current_user.id, refresh_updated: true)
|
|
38
|
+
# # 此时 user 的 is_deleted 变为 false,同时 deleted_at 和 deleted_by 会被清空,updated_by 会被更新
|
|
29
39
|
#
|
|
30
40
|
# 核心重要方法:
|
|
31
41
|
# - {#soft_delete}:执行软删除并调用 save 更新数据。
|
|
@@ -53,8 +63,8 @@ module ActivemodelObjectInfo
|
|
|
53
63
|
|
|
54
64
|
# 2. 注入 default_scope 默认查询作用域:如果模型拥有删除标记字段,则默认查询自动排除被标记的数据
|
|
55
65
|
default_scope do
|
|
56
|
-
|
|
57
|
-
where(deleted_field.to_sym => deleted_value_valid) if
|
|
66
|
+
current_model = try(:name).to_s.safe_constantize
|
|
67
|
+
where(deleted_field.to_sym => deleted_value_valid) if current_model.has_attribute?(deleted_field)
|
|
58
68
|
end
|
|
59
69
|
|
|
60
70
|
# 3. 定义底层的删除逻辑块 (给实例对象的内存属性赋值)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- shiner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|