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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80913d0545f39b6b56ea20826e6c5da37c3fbec7645f988c5439f85f6ac2eedc
4
- data.tar.gz: 8f839ed3448156219e6c4da35c9eb69d9eb7d4dca97ef48f1c89b937ea2a782e
3
+ metadata.gz: f0aebeaaaa8f81c3ee9b2107286f9cfa30ebbd6981ae8da0284bd71537f51a75
4
+ data.tar.gz: 042ee4576749af306512a90eee1cb3c50c3cacbb786204c9ae6af3a7f5cbd6e6
5
5
  SHA512:
6
- metadata.gz: a8c1005c3e1887dd377deb8ab79d1d9bad079dea09ac81e24ff484e59efdb44c3e3c6c7ba4741237954d944f0b5780ff00f9e7530ebe465c603eedc9f0aeb5a1
7
- data.tar.gz: 5bd16bc43f2c13b58bfa037ee6505777d80a97d455b7744d75f32cd928348a1d2d0e0cc638acc0070662b42792f83972c365ccd00c81860980f05196afc6bd32
6
+ metadata.gz: 401ebe7b53349260c17f509a4d61391f67880da739ed55ef386af77732d85341a5b27d59e905aed27b70990745a5608b45ed62e231965fe87e16caeae16fcc2b
7
+ data.tar.gz: bc40adcb1e297058f46b5c172cf28b68ebdab7693dfd57bead07bd9593509cf6c7372c99b9fcb3af12ace198880e92da737116d117e1345645e6dfb7e0cb1884
data/.rubocop.yml CHANGED
@@ -39,6 +39,7 @@ Metrics/ModuleLength:
39
39
  Max: 600
40
40
  Metrics/PerceivedComplexity:
41
41
  Max: 50
42
+
42
43
  Metrics/CyclomaticComplexity:
43
44
  Max: 50
44
45
 
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activemodel_object_info (0.4.0)
4
+ activemodel_object_info (0.4.1)
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.0'
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
- # Override at runtime (Supports Ruby 2.x Hash and Ruby 3.x Keyword Arguments)
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.0'
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
- # 运行时覆盖配置(完美兼容 Ruby 2.x Hash 传参和 Ruby 3.x 的关键字参数)
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
  ## 开发与测试
@@ -41,6 +41,6 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency 'simplecov'
42
42
  spec.add_development_dependency 'yard'
43
43
  spec.metadata = {
44
- 'rubygems_mfa_required' => 'false',
44
+ 'rubygems_mfa_required' => 'true',
45
45
  }
46
46
  end
@@ -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
- # 尝试获取当前类上配置的 INSTANCE_INFO 常量作为默认选项
72
- options = "#{self.class}::INSTANCE_INFO".safe_constantize || {} if options.blank?
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActivemodelObjectInfo
4
4
  module Version
5
- VERSION = '0.4.0'
5
+ VERSION = '0.4.1'
6
6
  end
7
7
  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.0
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: 'false'
173
+ rubygems_mfa_required: 'true'
174
174
  post_install_message:
175
175
  rdoc_options: []
176
176
  require_paths: