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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0aebeaaaa8f81c3ee9b2107286f9cfa30ebbd6981ae8da0284bd71537f51a75
4
- data.tar.gz: 042ee4576749af306512a90eee1cb3c50c3cacbb786204c9ae6af3a7f5cbd6e6
3
+ metadata.gz: 1096a56e9db6886e83460c0015079b2a583e940744d2be9b5bc2a2d6461943aa
4
+ data.tar.gz: c91020d37e00fad63a389db8a2246b7358ba814d303a08466614c34c0dad5c34
5
5
  SHA512:
6
- metadata.gz: 401ebe7b53349260c17f509a4d61391f67880da739ed55ef386af77732d85341a5b27d59e905aed27b70990745a5608b45ed62e231965fe87e16caeae16fcc2b
7
- data.tar.gz: bc40adcb1e297058f46b5c172cf28b68ebdab7693dfd57bead07bd9593509cf6c7372c99b9fcb3af12ace198880e92da737116d117e1345645e6dfb7e0cb1884
6
+ metadata.gz: 1e17eda2eab30babf92e4f802329f048a1f40ad2e2ea4e226a1671543243315262104a9f806569b032a106c9473c2d1f919017f52b667b32f9901fd25b4510ff
7
+ data.tar.gz: 54f2f7c502906709755d31cb7346bf195f3fba28bd9f0408b8c4f3049b0007bf9eea7c618f461621b9bdaccd2c31d23390fca8f9b3296566093c1c727ca865de
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activemodel_object_info (0.4.1)
4
+ activemodel_object_info (0.4.2)
5
5
  activesupport
6
6
 
7
7
  GEM
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.1'
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. Nested output for Associations (includes)
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
- # created_by, created_at, updated_by, updated_at, deleted(int), deleted_by, deleted_at
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
- t.operation_columns(:review, operator_prefix: 'my_', operator_suffix: '_user', timestamp_suffix: '_time')
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
- user.restore
206
+ deleted_user.restore
182
207
 
183
208
  # 6. Restore and refresh updated_at with user_id:
184
- user.restore(user_id: current_user.id, refresh_updated: true)
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.1'
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. 嵌套输出关联对象 (Associations)
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
- t.operation_columns(:review, operator_prefix: 'my_', operator_suffix: '_user', timestamp_suffix: '_time')
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
- # 此操作会将 deleted 标记重置为 0,并清空 deleted_by 和 deleted_at
179
- user.restore
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
- user.restore(user_id: current_user.id, refresh_updated: true)
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
- # @option keyword_options [Array<Symbol>] :only 给出具体可以用来输出的字段属性名数组。
64
- # @option keyword_options [Array<Symbol>] :except 给出需要被排除输出的字段属性名数组。
65
- # @option keyword_options [String, Symbol] :datetime_format 全局的时间格式设置。
66
- # @option keyword_options [Hash] :includes 需要嵌套输出的关联对象配置(如 { profile: { only: [:avatar] } })。
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
- context_constant_name = "#{self.class}::INSTANCE_INFO_#{context_name}"
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
- # 我们需要将 only_attributes 中的字段视为基础配置,以确保只包含这几个字段的正常输出
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
- # 特殊处理 abstract 类型:当类型为抽象类时,由于可能没有对应的真实属性方法,直接赋值为 nil,后续依赖 filter 来生成实际值
208
+ # 当类型为抽象类时,是不会有对应的方法和属性的,因此直接设置为 nil 并期待之后的 filter 等规则来生成实际值。
137
209
  v = current_attr_config[:type] == :abstract ? nil : __send__(k)
138
210
 
139
211
  # 跳过条件:既不在最终输出名单中,也不是显式声明的 abstract/method 虚拟字段
140
- next unless output_attributes.include?(k) || %i[abstract method].include?(current_attr_config[:type])
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
- # @param [Hash] result 当前实例的输出结果散列
188
- # @param [Hash] options 包含 :includes 配置的选项散列
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
- # # 软删除一条记录(必须要传入 user_id)
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
- _current_model = try(:name).to_s.safe_constantize
57
- where(deleted_field.to_sym => deleted_value_valid) if _current_model.has_attribute?(deleted_field)
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. 定义底层的删除逻辑块 (给实例对象的内存属性赋值)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActivemodelObjectInfo
4
4
  module Version
5
- VERSION = '0.4.1'
5
+ VERSION = '0.4.2'
6
6
  end
7
7
  end
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.1
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-28 00:00:00.000000000 Z
11
+ date: 2026-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport