activestorage_saas 7.0.4 → 7.2.3

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: d2582d10000f7aae9780b0d273c172c4a76cb08c97bf092126979ce4110d00d8
4
- data.tar.gz: 7f88a5f75a7a18bf1587d114e2b87d7d9888afd633d88488868910b495b2c72a
3
+ metadata.gz: 422aa00826004eda3168829ac76146aa61a20934de7e41cd05ec8f4eccd4c6eb
4
+ data.tar.gz: 8ff8749becec2ae3ed721ee1cfe1fe1e2dcaa3e1658c06364c4304d452f131b7
5
5
  SHA512:
6
- metadata.gz: f7d76f176c4a9c6ed75544f7008d874363c0cd9b80a372af94d93c8d24800835644fc18ed62e96546e4ea924491b789e8ec07e676e3aaa9bbc1e7b19bff4aad0
7
- data.tar.gz: 5d9ed9018b5a423dc66ed88bec3b93e34abd12d060c11ca245767a9eb91aa22bf2115bbef8e73e1c5fdedf17a698c139303a641dd1da81ddbc617a30ce7fb998
6
+ metadata.gz: d4d504753d8951ad1d8e932d417590b3bf8d916f84ceb3ec09438203158a59192c8e183a943b8d9d3c07a98db38803f9d55579da9079b0861b9dd17cf832cb02
7
+ data.tar.gz: 66e2ce4a52e23ebf32dcddbb3046e8e51cd3c8b8580d1c94d4713605de2e9f9863d5150c445a801900c993f11bbb8868c0d398b70604ec6b6fd5467996e88118
data/AGENTS.md ADDED
@@ -0,0 +1,260 @@
1
+ # ActiveStorageSaas - AI 助手指南
2
+
3
+ 本文档用于指导 AI 助手理解 ActiveStorageSaas gem 的代码结构、依赖关系和升级流程。
4
+
5
+ ## 项目概述
6
+
7
+ ActiveStorageSaas 是一个 Rails gem,为 ActiveStorage 提供多租户存储服务支持。它通过扩展 ActiveStorage 的核心类来实现动态存储服务加载。
8
+
9
+ ## 核心架构
10
+
11
+ ### 1. 主要组件
12
+
13
+ #### SaasService (`lib/active_storage/service/saas_service.rb`)
14
+ - **作用**:包装器服务,动态委托方法调用到实际的存储服务
15
+ - **关键特性**:
16
+ - 通过 `Service.public_instance_methods(false)` 动态定义所有 Service 方法
17
+ - 使用 `method_missing` 处理未定义的方法
18
+ - 通过 `service_resolver` 根据 blob 动态解析实际服务
19
+ - **依赖的 Rails API**:
20
+ - `ActiveStorage::Service` 基类
21
+ - `Service.public_instance_methods` 方法
22
+ - `url_for_direct_upload` 和 `headers_for_direct_upload` 方法(需要支持 `custom_metadata` 参数)
23
+
24
+ #### BlobModelMixin (`lib/active_storage_saas/blob_model_mixin.rb`)
25
+ - **作用**:扩展 `ActiveStorage::Blob`,重写 `service` 方法以支持动态服务解析
26
+ - **关键方法**:
27
+ - `service`:重写以支持动态服务解析
28
+ - `analyzer_class`:支持服务级别的 analyzers
29
+ - `validate_service_name_in_services`:验证服务名称
30
+ - **依赖的 Rails API**:
31
+ - `ActiveStorage::Blob#service` 方法
32
+ - `ActiveStorage::Blob#service_name` 属性
33
+ - `ActiveSupport.on_load(:active_storage_blob)` 钩子
34
+ - `redefine_method` (ActiveSupport 扩展)
35
+
36
+ #### DirectUploadsControllerMixin (`lib/active_storage_saas/direct_uploads_controller_mixin.rb`)
37
+ - **作用**:扩展 `ActiveStorage::DirectUploadsController`,支持动态服务名称
38
+ - **关键方法**:
39
+ - `create`:重写以使用 `create_before_direct_upload!` 并传递 `service_name`
40
+ - `blob_args`:合并额外的 blob 参数和服务名称
41
+ - **依赖的 Rails API**:
42
+ - `ActiveStorage::DirectUploadsController#create` 方法
43
+ - `ActiveStorage::Blob.create_before_direct_upload!` 方法
44
+ - `blob_args` 方法签名
45
+
46
+ #### StorageServiceConfigurationMixin (`lib/active_storage_saas/storage_service_configuration_model_mixin.rb`)
47
+ - **作用**:为存储服务配置模型提供方法
48
+ - **关键方法**:
49
+ - `to_service`:根据配置创建存储服务实例
50
+ - `to_service_name`:生成格式为 `ClassName:ID` 的服务名称
51
+ - `from_service_name`:从服务名称解析配置记录
52
+ - **依赖的 Rails API**:
53
+ - `ActiveStorage::Blob.services` 和 `services.fetch`
54
+ - `ActiveStorage::Blob.services.send(:configurations)`
55
+
56
+ #### Engine (`lib/active_storage_saas/engine.rb`)
57
+ - **作用**:Rails Engine,负责加载 mixins 和配置
58
+ - **关键初始化**:
59
+ - 使用 `ActiveSupport.on_load(:active_storage_blob)` 加载 BlobModelMixin
60
+ - 使用 `config.to_prepare` 加载 DirectUploadsControllerMixin
61
+ - 配置默认的服务解析器、转换器等
62
+
63
+ ## 依赖的 Rails 版本和 API
64
+
65
+ ### 当前版本
66
+ - **Rails**: 7.2.3
67
+ - **ActiveStorage**: 7.2.3
68
+ - **Ruby**: >= 2.6.0
69
+
70
+ ### 关键依赖的 Rails API
71
+
72
+ 在升级时需要检查以下 API 是否发生变化:
73
+
74
+ 1. **ActiveStorage::Service**
75
+ - `public_instance_methods(false)` - 用于动态方法定义
76
+ - `url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})`
77
+ - `headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:, custom_metadata: {})`
78
+ - `name=` 和 `name` 方法
79
+
80
+ 2. **ActiveStorage::Blob**
81
+ - `service` 方法(返回服务实例)
82
+ - `service_name` 属性
83
+ - `create_before_direct_upload!` 方法签名
84
+ - `services` 类属性(Registry 对象)
85
+ - `services.fetch(service_name)` 方法
86
+ - `services.send(:configurations)` 方法
87
+
88
+ 3. **ActiveStorage::DirectUploadsController**
89
+ - `create` 方法
90
+ - `blob_args` 方法(返回 Hash)
91
+ - `direct_upload_json` 方法
92
+
93
+ 4. **ActiveSupport**
94
+ - `on_load(:active_storage_blob)` 钩子
95
+ - `redefine_method` 方法(Module 扩展)
96
+ - `ruby2_keywords`(Ruby 2.x 兼容性,Ruby 3.x 会忽略)
97
+
98
+ ## 升级检查清单
99
+
100
+ 当需要升级 Rails 版本时,AI 助手应该执行以下检查:
101
+
102
+ ### 1. 更新依赖版本
103
+
104
+ ```ruby
105
+ # activestorage_saas.gemspec
106
+ spec.version = "X.Y.Z" # 更新为新版本
107
+ spec.add_dependency "activestorage", 'X.Y.Z' # 更新为对应的 Rails 版本
108
+
109
+ # Gemfile
110
+ gem "rails", "X.Y.Z" # 更新为新版本
111
+ ```
112
+
113
+ ### 2. 检查 Service 类变更
114
+
115
+ **文件**: `/Users/xiaohui/Codes/rails/activestorage/lib/active_storage/service.rb`
116
+
117
+ 检查点:
118
+ - `url_for_direct_upload` 方法签名是否变化(特别是 `custom_metadata` 参数)
119
+ - `headers_for_direct_upload` 方法签名是否变化
120
+ - `public_instance_methods(false)` 是否仍然可用
121
+ - 是否有新的公共方法需要委托
122
+
123
+ **影响文件**:
124
+ - `lib/active_storage/service/saas_service.rb`
125
+
126
+ ### 3. 检查 Blob 类变更
127
+
128
+ **文件**: `/Users/xiaohui/Codes/rails/activestorage/app/models/active_storage/blob.rb`
129
+
130
+ 检查点:
131
+ - `service` 方法实现是否变化
132
+ - `create_before_direct_upload!` 方法签名是否变化
133
+ - `services` 类属性的类型和接口是否变化
134
+ - `services.fetch` 和 `services.send(:configurations)` 是否仍然可用
135
+ - 验证逻辑是否变化(`validate do` 块)
136
+
137
+ **影响文件**:
138
+ - `lib/active_storage_saas/blob_model_mixin.rb`
139
+
140
+ ### 4. 检查 DirectUploadsController 变更
141
+
142
+ **文件**: `/Users/xiaohui/Codes/rails/activestorage/app/controllers/active_storage/direct_uploads_controller.rb`
143
+
144
+ 检查点:
145
+ - `create` 方法实现是否变化
146
+ - `blob_args` 方法返回格式是否变化
147
+ - `create_before_direct_upload!` 调用方式是否变化(是否使用 `**blob_args` 展开)
148
+
149
+ **影响文件**:
150
+ - `lib/active_storage_saas/direct_uploads_controller_mixin.rb`
151
+
152
+ ### 5. 检查 ActiveSupport 钩子
153
+
154
+ **文件**: `/Users/xiaohui/Codes/rails/activestorage/lib/active_storage/engine.rb`
155
+
156
+ 检查点:
157
+ - `ActiveSupport.on_load(:active_storage_blob)` 是否仍然可用
158
+ - 钩子触发时机是否变化
159
+
160
+ **影响文件**:
161
+ - `lib/active_storage_saas/engine.rb`
162
+
163
+ ### 6. 检查 ActiveSupport 扩展
164
+
165
+ **文件**: `/Users/xiaohui/Codes/rails/activesupport/lib/active_support/core_ext/module/redefine_method.rb`
166
+
167
+ 检查点:
168
+ - `redefine_method` 是否仍然可用
169
+ - 方法签名是否变化
170
+
171
+ **影响文件**:
172
+ - `lib/active_storage_saas/blob_model_mixin.rb`
173
+ - `lib/active_storage/service/saas_service.rb`
174
+
175
+ ### 7. 检查方法委托机制
176
+
177
+ 检查 `ruby2_keywords` 的使用:
178
+ - Ruby 3.x 会自动忽略 `ruby2_keywords`,但保留它不会造成问题
179
+ - 如果 Rails 移除了对 Ruby 2.x 的支持,可以考虑移除
180
+
181
+ **影响文件**:
182
+ - `lib/active_storage/service/saas_service.rb`
183
+
184
+ ## 升级工作流程
185
+
186
+ ### 步骤 1: 定位 Rails 源码
187
+
188
+ ```bash
189
+ # 假设 Rails 源码在 /Users/xiaohui/Codes/rails
190
+ cd /Users/xiaohui/Codes/rails
191
+ git checkout v7.2.3 # 切换到目标版本
192
+ ```
193
+
194
+ ### 步骤 2: 对比关键文件
195
+
196
+ 使用 `grep` 和 `read_file` 工具检查关键文件:
197
+
198
+ 1. 读取 Rails 源码中的相关文件
199
+ 2. 对比当前 gem 代码
200
+ 3. 识别变更点
201
+
202
+ ### 步骤 3: 更新代码
203
+
204
+ 根据检查结果更新:
205
+
206
+ 1. **SaasService**: 如果 Service 类有新方法,动态委托会自动处理;如果方法签名变化,需要更新
207
+ 2. **BlobModelMixin**: 如果 `service` 方法实现变化,需要更新重写逻辑
208
+ 3. **DirectUploadsControllerMixin**: 如果 `create` 或 `blob_args` 变化,需要更新
209
+ 4. **Engine**: 如果钩子机制变化,需要更新初始化逻辑
210
+
211
+ ### 步骤 4: 测试兼容性
212
+
213
+ 运行测试套件:
214
+
215
+ ```bash
216
+ bundle exec rspec
217
+ ```
218
+
219
+ ### 步骤 5: 更新文档
220
+
221
+ 更新 `README.md` 和 `CHANGELOG.md` 中的版本信息。
222
+
223
+ ## 常见问题
224
+
225
+ ### Q: 如何知道 Rails 版本是否兼容?
226
+
227
+ A: 检查以下关键点:
228
+ 1. `ActiveStorage::Service` 的公共方法列表
229
+ 2. `ActiveStorage::Blob#service` 方法实现
230
+ 3. `ActiveStorage::DirectUploadsController#create` 方法实现
231
+ 4. ActiveSupport 钩子是否仍然可用
232
+
233
+ ### Q: 如果 Rails 移除了某个方法怎么办?
234
+
235
+ A:
236
+ 1. 检查是否有替代方法
237
+ 2. 如果必须保留功能,考虑在 gem 中实现兼容层
238
+ 3. 更新文档说明兼容性要求
239
+
240
+ ### Q: 如何测试升级后的兼容性?
241
+
242
+ A:
243
+ 1. 运行完整的测试套件
244
+ 2. 在测试应用中创建 blob 并验证服务解析
245
+ 3. 测试直接上传功能
246
+ 4. 测试多租户场景
247
+
248
+ ## 代码修改原则
249
+
250
+ 1. **最小侵入**:尽量使用 mixin 和 prepend,避免直接修改 Rails 代码
251
+ 2. **向后兼容**:保留对旧版本 Ruby/Rails 的支持(如果可能)
252
+ 3. **委托优先**:通过方法委托实现功能,而不是复制代码
253
+ 4. **测试覆盖**:确保所有关键功能都有测试覆盖
254
+
255
+ ## 参考资源
256
+
257
+ - Rails ActiveStorage 文档: https://guides.rubyonrails.org/active_storage_overview.html
258
+ - Rails 源码: `/Users/xiaohui/Codes/rails`
259
+ - 当前 gem 版本: 7.2.3
260
+
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [7.2.3] - 2024-XX-XX
4
+
5
+ ### Changed
6
+ - 升级支持 Rails 7.2.3
7
+ - 更新 `DirectUploadsControllerMixin` 使用 `create_before_direct_upload!` 方法,与 Rails 7.2.3 保持一致
8
+ - 更新依赖版本:activestorage 7.2.3
9
+
10
+ ### Compatibility
11
+ - Rails: 7.2.3
12
+ - ActiveStorage: 7.2.3
13
+ - Ruby: >= 2.6.0
14
+
3
15
  ## [0.1.0] - 2022-08-09
4
16
 
5
17
  - Initial release
data/COVERAGE.md ADDED
@@ -0,0 +1,64 @@
1
+ # 代码覆盖率
2
+
3
+ 本项目使用 [SimpleCov](https://github.com/simplecov-ruby/simplecov) 来跟踪代码覆盖率。
4
+
5
+ ## 覆盖率要求
6
+
7
+ - **最小行覆盖率**: 90%
8
+ - **最小文件覆盖率**: 80%
9
+
10
+ ## 运行覆盖率测试
11
+
12
+ ### 使用环境变量
13
+
14
+ ```bash
15
+ COVERAGE=true bundle exec rspec
16
+ ```
17
+
18
+ ### 使用 Rake 任务
19
+
20
+ ```bash
21
+ # 运行测试并生成覆盖率报告
22
+ bundle exec rake spec:coverage
23
+
24
+ # 运行测试并验证覆盖率(如果低于 90% 会失败)
25
+ bundle exec rake spec:coverage:verify
26
+ ```
27
+
28
+ ### 使用脚本
29
+
30
+ ```bash
31
+ ./script/check_coverage
32
+ ```
33
+
34
+ ## 查看覆盖率报告
35
+
36
+ 运行测试后,覆盖率报告会生成在 `coverage/index.html`。在浏览器中打开该文件即可查看详细的覆盖率报告。
37
+
38
+ ## 覆盖率统计范围
39
+
40
+ 覆盖率统计只包括 `lib/` 目录下的代码,排除:
41
+ - 测试文件 (`spec/`)
42
+ - 配置文件 (`config/`)
43
+ - 生成器代码 (`generators/`)
44
+ - 测试应用代码 (`spec/dummy/`)
45
+ - 其他非核心代码
46
+
47
+ ## CI/CD 集成
48
+
49
+ 项目已配置 GitHub Actions 工作流 (`.github/workflows/coverage.yml`),在每次推送或创建 Pull Request 时会:
50
+ 1. 运行测试套件
51
+ 2. 生成覆盖率报告
52
+ 3. 验证覆盖率是否达到 90%
53
+ 4. 上传覆盖率报告作为构建产物
54
+
55
+ ## 提高覆盖率
56
+
57
+ 如果覆盖率低于 90%,请:
58
+ 1. 查看 `coverage/index.html` 了解哪些代码未被覆盖
59
+ 2. 为未覆盖的代码添加测试用例
60
+ 3. 特别关注边界情况和错误处理路径
61
+
62
+ ## 当前覆盖率
63
+
64
+ 运行 `COVERAGE=true bundle exec rspec` 后,会在终端显示当前覆盖率。
data/Gemfile CHANGED
@@ -5,11 +5,12 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in activestorage_saas.gemspec
6
6
  gemspec
7
7
 
8
- gem "rails", "~> 7.0"
8
+ gem "rails", "7.2.3"
9
9
  gem "sqlite3"
10
10
  gem "propshaft"
11
- gem "rspec-rails", "~> 5.1"
11
+ gem 'rspec-rails', '~> 8.0', '>= 8.0.2'
12
+ gem 'aws-sdk-s3', '~> 1.206'
12
13
 
13
- gem "guard-rspec", "~> 4.7"
14
-
15
- gem "aws-sdk-s3", "~> 1.114"
14
+ group :development, :test do
15
+ gem 'simplecov', '~> 0.22', require: false
16
+ end
data/README.md CHANGED
@@ -1,33 +1,222 @@
1
- # ActivestorageSaas
1
+ # ActiveStorageSaas
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/activestorage_saas`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ActiveStorageSaas 是一个 Rails gem,为 ActiveStorage 提供多租户存储服务支持。它允许每个租户配置自己的存储服务,并支持动态加载不同的存储服务。
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## 功能特性
6
6
 
7
- ## Installation
7
+ - 🏢 **多租户支持**:每个租户可以配置独立的存储服务
8
+ - 🔄 **动态服务加载**:根据 blob 的 service_name 动态解析并加载对应的存储服务
9
+ - 🔌 **灵活配置**:支持通过数据库配置存储服务选项
10
+ - 📦 **完全兼容**:与 Rails ActiveStorage API 完全兼容,无需修改现有代码
11
+ - 🚀 **直接上传支持**:支持 ActiveStorage 的直接上传功能
8
12
 
9
- Install the gem and add to the application's Gemfile by executing:
13
+ ## 安装
10
14
 
11
- $ bundle add activestorage_saas
15
+ Gemfile 中添加:
12
16
 
13
- If bundler is not being used to manage dependencies, install the gem by executing:
17
+ ```ruby
18
+ gem 'activestorage_saas', '~> 7.2.3'
19
+ ```
14
20
 
15
- $ gem install activestorage_saas
21
+ 然后运行:
16
22
 
17
- ## Usage
23
+ ```bash
24
+ bundle install
25
+ ```
18
26
 
19
- TODO: Write usage instructions here
27
+ 运行安装生成器:
20
28
 
21
- ## Development
29
+ ```bash
30
+ rails generate active_storage_saas:install
31
+ ```
22
32
 
23
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+ 这将创建必要的迁移文件。运行迁移:
24
34
 
25
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+ ```bash
36
+ rails db:migrate
37
+ ```
26
38
 
27
- ## Contributing
39
+ ## 配置
28
40
 
29
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activestorage_saas.
41
+ ### 1. 数据库迁移
30
42
 
31
- ## License
43
+ 安装生成器会创建一个 `storage_service_configurations` 表,用于存储每个租户的存储服务配置。
32
44
 
33
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
45
+ ### 2. 存储配置 (config/storage.yml)
46
+
47
+ 在 `config/storage.yml` 中配置一个 `saas` 服务作为包装器:
48
+
49
+ ```yaml
50
+ saas:
51
+ service: Saas
52
+ default_service: local # 默认使用的存储服务
53
+
54
+ local:
55
+ service: Disk
56
+ root: <%= Rails.root.join("storage") %>
57
+
58
+ amazon:
59
+ service: S3
60
+ access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
61
+ secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
62
+ region: us-east-1
63
+ bucket: my-bucket
64
+ ```
65
+
66
+ ### 3. 应用配置 (config/application.rb 或 config/environments/*.rb)
67
+
68
+ ```ruby
69
+ Rails.application.configure do
70
+ # 可选:自定义服务解析器
71
+ # config.active_storage_saas.service_resolver = ->(blob) {
72
+ # StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service
73
+ # }
74
+
75
+ # 可选:自定义服务名称转换器(用于直接上传)
76
+ # config.active_storage_saas.service_name_converter = ->(controller) {
77
+ # controller.send(:current_tenant).storage_service&.to_service_name
78
+ # }
79
+
80
+ # 可选:自定义服务名称验证器
81
+ # config.active_storage_saas.service_name_validator = ->(service_name) {
82
+ # StorageServiceConfiguration.valid_service_name?(service_name)
83
+ # }
84
+
85
+ # 可选:直接上传时额外的 blob 参数
86
+ # config.active_storage_saas.direct_upload_extra_blob_args = ->(controller) {
87
+ # { tenant: controller.send(:current_tenant) }
88
+ # }
89
+ end
90
+ ```
91
+
92
+ ## 使用方法
93
+
94
+ ### 创建存储服务配置
95
+
96
+ ```ruby
97
+ # 为租户创建存储服务配置
98
+ config = StorageServiceConfiguration.create!(
99
+ service_name: 'amazon', # 基础服务名称(在 storage.yml 中定义)
100
+ service_options: {
101
+ bucket: 'tenant-specific-bucket',
102
+ region: 'us-west-2'
103
+ }
104
+ )
105
+
106
+ # 获取服务名称(格式:StorageServiceConfiguration:123)
107
+ service_name = config.to_service_name
108
+ ```
109
+
110
+ ### 使用存储服务
111
+
112
+ ```ruby
113
+ # 创建 blob 时指定服务名称
114
+ blob = ActiveStorage::Blob.create!(
115
+ filename: 'example.jpg',
116
+ byte_size: 1024,
117
+ checksum: 'abc123',
118
+ content_type: 'image/jpeg',
119
+ service_name: config.to_service_name # 使用动态服务名称
120
+ )
121
+
122
+ # blob.service 会自动解析为对应的存储服务实例
123
+ blob.service # => 返回配置的 S3 服务实例
124
+ ```
125
+
126
+ ### 在模型中关联文件
127
+
128
+ ```ruby
129
+ class User < ApplicationRecord
130
+ has_one_attached :avatar
131
+ end
132
+
133
+ # 上传文件时指定服务
134
+ user.avatar.attach(
135
+ io: file,
136
+ filename: 'avatar.jpg',
137
+ service_name: current_tenant.storage_config.to_service_name
138
+ )
139
+ ```
140
+
141
+ ## 工作原理
142
+
143
+ ### SaasService
144
+
145
+ `SaasService` 是一个包装器服务,它:
146
+
147
+ 1. 接收一个 `blob` 对象
148
+ 2. 根据 `blob.service_name` 动态解析对应的存储服务
149
+ 3. 将所有方法调用委托给实际的存储服务
150
+
151
+ ### 服务解析流程
152
+
153
+ 1. 当访问 `blob.service` 时,会调用 `ActiveStorageSaas.service_resolver`
154
+ 2. 解析器根据 `service_name` 查找对应的 `StorageServiceConfiguration`
155
+ 3. 配置对象通过 `to_service` 方法创建实际的存储服务实例
156
+ 4. 如果解析失败,回退到默认服务
157
+
158
+ ### 服务名称格式
159
+
160
+ 动态服务的名称格式为:`ClassName:ID`,例如:
161
+ - `StorageServiceConfiguration:1`
162
+ - `StorageServiceConfiguration:42`
163
+
164
+ 这种格式允许从服务名称中提取配置记录的 ID。
165
+
166
+ ## 高级用法
167
+
168
+ ### 自定义服务解析器
169
+
170
+ ```ruby
171
+ config.active_storage_saas.service_resolver = ->(blob) {
172
+ tenant = blob.record&.tenant
173
+ tenant&.storage_config&.to_service
174
+ }
175
+ ```
176
+
177
+ ### 多租户集成示例
178
+
179
+ ```ruby
180
+ class Tenant < ApplicationRecord
181
+ has_one :storage_service_configuration, class_name: 'StorageServiceConfiguration'
182
+ end
183
+
184
+ class ApplicationController < ActionController::Base
185
+ def current_tenant
186
+ @current_tenant ||= Tenant.find(session[:tenant_id])
187
+ end
188
+ end
189
+
190
+ # 在配置中
191
+ config.active_storage_saas.service_name_converter = ->(controller) {
192
+ controller.current_tenant.storage_service_configuration.to_service_name
193
+ }
194
+ ```
195
+
196
+ ## 兼容性
197
+
198
+ - **Rails 版本**:7.2.3
199
+ - **Ruby 版本**:>= 2.6.0
200
+ - **ActiveStorage 版本**:7.2.3
201
+
202
+ ## 开发
203
+
204
+ 运行测试:
205
+
206
+ ```bash
207
+ bundle exec rspec
208
+ ```
209
+
210
+ 运行控制台:
211
+
212
+ ```bash
213
+ bin/console
214
+ ```
215
+
216
+ ## 贡献
217
+
218
+ 欢迎提交 Issue 和 Pull Request!
219
+
220
+ ## 许可证
221
+
222
+ MIT License
@@ -12,7 +12,7 @@ module ActiveStorageSaas
12
12
 
13
13
  module InstanceMethods
14
14
  def create
15
- blob = ActiveStorage::Blob.create!(blob_args)
15
+ blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
16
16
  render json: direct_upload_json(blob)
17
17
  end
18
18
 
@@ -30,7 +30,8 @@ module ActiveStorageSaas
30
30
  def to_service
31
31
  defined_service = ActiveStorage::Blob.services.fetch(service_name)
32
32
  klass = defined_service.class
33
- options = ActiveStorage::Blob.services.send(:configurations).fetch(service_name.to_sym)
33
+ options = ActiveStorage::Blob.services.send(:configurations).fetch(service_name.to_sym).deep_dup
34
+ options.delete(:service) if service_name == 'amazon'
34
35
  options.deep_merge! service_options.deep_symbolize_keys
35
36
  klass.new(**options).tap do |instance|
36
37
  instance.instance_eval <<~RUBY
metadata CHANGED
@@ -1,35 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage_saas
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.4
4
+ version: 7.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - xiaohui
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-22 00:00:00.000000000 Z
11
+ date: 2025-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activestorage
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.3.1
20
- - - "<="
21
- - !ruby/object:Gem::Version
22
- version: 7.0.4
19
+ version: 7.2.3
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 7.0.3.1
30
- - - "<="
24
+ - - '='
31
25
  - !ruby/object:Gem::Version
32
- version: 7.0.4
26
+ version: 7.2.3
33
27
  description: Each tenant can set its own storage service, ActiveStorage service can
34
28
  be dynamically loaded.
35
29
  email:
@@ -38,17 +32,12 @@ executables: []
38
32
  extensions: []
39
33
  extra_rdoc_files: []
40
34
  files:
41
- - ".rspec"
42
- - ".rubocop.yml"
35
+ - AGENTS.md
43
36
  - CHANGELOG.md
37
+ - COVERAGE.md
44
38
  - Gemfile
45
- - Gemfile.lock
46
- - Guardfile
47
39
  - LICENSE.txt
48
40
  - README.md
49
- - Rakefile
50
- - activestorage_saas.gemspec
51
- - config/initializers/active_storage_saas.rb
52
41
  - lib/active_storage/service/saas_service.rb
53
42
  - lib/active_storage_saas.rb
54
43
  - lib/active_storage_saas/blob_model_mixin.rb
@@ -60,7 +49,6 @@ files:
60
49
  - lib/generators/active_storage_saas/install/USAGE
61
50
  - lib/generators/active_storage_saas/install/install_generator.rb
62
51
  - lib/generators/active_storage_saas/install/templates/config/initializers/active_storage_saas.rb
63
- - sig/activestorage_saas.rbs
64
52
  homepage: https://github.com/xiaohui-zhangxh/activestorage_saas
65
53
  licenses:
66
54
  - MIT
@@ -83,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
71
  - !ruby/object:Gem::Version
84
72
  version: '0'
85
73
  requirements: []
86
- rubygems_version: 3.3.7
74
+ rubygems_version: 3.5.16
87
75
  signing_key:
88
76
  specification_version: 4
89
77
  summary: Wraps multi-tenant storage services as ActiveStorage service
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,17 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.6
3
- NewCops: enable
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: single_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
14
-
15
- Layout/IndentationConsistency:
16
- Enabled: true
17
- EnforcedStyle: indented_internal_methods
data/Gemfile.lock DELETED
@@ -1,236 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- activestorage_saas (7.0.4)
5
- activestorage (>= 7.0.3.1, <= 7.0.4)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- actioncable (7.0.4)
11
- actionpack (= 7.0.4)
12
- activesupport (= 7.0.4)
13
- nio4r (~> 2.0)
14
- websocket-driver (>= 0.6.1)
15
- actionmailbox (7.0.4)
16
- actionpack (= 7.0.4)
17
- activejob (= 7.0.4)
18
- activerecord (= 7.0.4)
19
- activestorage (= 7.0.4)
20
- activesupport (= 7.0.4)
21
- mail (>= 2.7.1)
22
- net-imap
23
- net-pop
24
- net-smtp
25
- actionmailer (7.0.4)
26
- actionpack (= 7.0.4)
27
- actionview (= 7.0.4)
28
- activejob (= 7.0.4)
29
- activesupport (= 7.0.4)
30
- mail (~> 2.5, >= 2.5.4)
31
- net-imap
32
- net-pop
33
- net-smtp
34
- rails-dom-testing (~> 2.0)
35
- actionpack (7.0.4)
36
- actionview (= 7.0.4)
37
- activesupport (= 7.0.4)
38
- rack (~> 2.0, >= 2.2.0)
39
- rack-test (>= 0.6.3)
40
- rails-dom-testing (~> 2.0)
41
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
42
- actiontext (7.0.4)
43
- actionpack (= 7.0.4)
44
- activerecord (= 7.0.4)
45
- activestorage (= 7.0.4)
46
- activesupport (= 7.0.4)
47
- globalid (>= 0.6.0)
48
- nokogiri (>= 1.8.5)
49
- actionview (7.0.4)
50
- activesupport (= 7.0.4)
51
- builder (~> 3.1)
52
- erubi (~> 1.4)
53
- rails-dom-testing (~> 2.0)
54
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
55
- activejob (7.0.4)
56
- activesupport (= 7.0.4)
57
- globalid (>= 0.3.6)
58
- activemodel (7.0.4)
59
- activesupport (= 7.0.4)
60
- activerecord (7.0.4)
61
- activemodel (= 7.0.4)
62
- activesupport (= 7.0.4)
63
- activestorage (7.0.4)
64
- actionpack (= 7.0.4)
65
- activejob (= 7.0.4)
66
- activerecord (= 7.0.4)
67
- activesupport (= 7.0.4)
68
- marcel (~> 1.0)
69
- mini_mime (>= 1.1.0)
70
- activesupport (7.0.4)
71
- concurrent-ruby (~> 1.0, >= 1.0.2)
72
- i18n (>= 1.6, < 2)
73
- minitest (>= 5.1)
74
- tzinfo (~> 2.0)
75
- aws-eventstream (1.2.0)
76
- aws-partitions (1.664.0)
77
- aws-sdk-core (3.168.1)
78
- aws-eventstream (~> 1, >= 1.0.2)
79
- aws-partitions (~> 1, >= 1.651.0)
80
- aws-sigv4 (~> 1.5)
81
- jmespath (~> 1, >= 1.6.1)
82
- aws-sdk-kms (1.59.0)
83
- aws-sdk-core (~> 3, >= 3.165.0)
84
- aws-sigv4 (~> 1.1)
85
- aws-sdk-s3 (1.117.1)
86
- aws-sdk-core (~> 3, >= 3.165.0)
87
- aws-sdk-kms (~> 1)
88
- aws-sigv4 (~> 1.4)
89
- aws-sigv4 (1.5.2)
90
- aws-eventstream (~> 1, >= 1.0.2)
91
- builder (3.2.4)
92
- coderay (1.1.3)
93
- concurrent-ruby (1.1.10)
94
- crass (1.0.6)
95
- diff-lcs (1.5.0)
96
- erubi (1.11.0)
97
- ffi (1.15.5)
98
- formatador (1.1.0)
99
- globalid (1.0.0)
100
- activesupport (>= 5.0)
101
- guard (2.18.0)
102
- formatador (>= 0.2.4)
103
- listen (>= 2.7, < 4.0)
104
- lumberjack (>= 1.0.12, < 2.0)
105
- nenv (~> 0.1)
106
- notiffany (~> 0.0)
107
- pry (>= 0.13.0)
108
- shellany (~> 0.0)
109
- thor (>= 0.18.1)
110
- guard-compat (1.2.1)
111
- guard-rspec (4.7.3)
112
- guard (~> 2.1)
113
- guard-compat (~> 1.1)
114
- rspec (>= 2.99.0, < 4.0)
115
- i18n (1.12.0)
116
- concurrent-ruby (~> 1.0)
117
- jmespath (1.6.1)
118
- listen (3.7.1)
119
- rb-fsevent (~> 0.10, >= 0.10.3)
120
- rb-inotify (~> 0.9, >= 0.9.10)
121
- loofah (2.19.0)
122
- crass (~> 1.0.2)
123
- nokogiri (>= 1.5.9)
124
- lumberjack (1.2.8)
125
- mail (2.7.1)
126
- mini_mime (>= 0.1.1)
127
- marcel (1.0.2)
128
- method_source (1.0.0)
129
- mini_mime (1.1.2)
130
- mini_portile2 (2.8.0)
131
- minitest (5.16.3)
132
- nenv (0.3.0)
133
- net-imap (0.3.1)
134
- net-protocol
135
- net-pop (0.1.2)
136
- net-protocol
137
- net-protocol (0.1.3)
138
- timeout
139
- net-smtp (0.3.3)
140
- net-protocol
141
- nio4r (2.5.8)
142
- nokogiri (1.13.9)
143
- mini_portile2 (~> 2.8.0)
144
- racc (~> 1.4)
145
- notiffany (0.1.3)
146
- nenv (~> 0.1)
147
- shellany (~> 0.0)
148
- propshaft (0.6.4)
149
- actionpack (>= 7.0.0)
150
- activesupport (>= 7.0.0)
151
- rack
152
- railties (>= 7.0.0)
153
- pry (0.14.1)
154
- coderay (~> 1.1)
155
- method_source (~> 1.0)
156
- racc (1.6.0)
157
- rack (2.2.4)
158
- rack-test (2.0.2)
159
- rack (>= 1.3)
160
- rails (7.0.4)
161
- actioncable (= 7.0.4)
162
- actionmailbox (= 7.0.4)
163
- actionmailer (= 7.0.4)
164
- actionpack (= 7.0.4)
165
- actiontext (= 7.0.4)
166
- actionview (= 7.0.4)
167
- activejob (= 7.0.4)
168
- activemodel (= 7.0.4)
169
- activerecord (= 7.0.4)
170
- activestorage (= 7.0.4)
171
- activesupport (= 7.0.4)
172
- bundler (>= 1.15.0)
173
- railties (= 7.0.4)
174
- rails-dom-testing (2.0.3)
175
- activesupport (>= 4.2.0)
176
- nokogiri (>= 1.6)
177
- rails-html-sanitizer (1.4.3)
178
- loofah (~> 2.3)
179
- railties (7.0.4)
180
- actionpack (= 7.0.4)
181
- activesupport (= 7.0.4)
182
- method_source
183
- rake (>= 12.2)
184
- thor (~> 1.0)
185
- zeitwerk (~> 2.5)
186
- rake (13.0.6)
187
- rb-fsevent (0.11.2)
188
- rb-inotify (0.10.1)
189
- ffi (~> 1.0)
190
- rspec (3.12.0)
191
- rspec-core (~> 3.12.0)
192
- rspec-expectations (~> 3.12.0)
193
- rspec-mocks (~> 3.12.0)
194
- rspec-core (3.12.0)
195
- rspec-support (~> 3.12.0)
196
- rspec-expectations (3.12.0)
197
- diff-lcs (>= 1.2.0, < 2.0)
198
- rspec-support (~> 3.12.0)
199
- rspec-mocks (3.12.0)
200
- diff-lcs (>= 1.2.0, < 2.0)
201
- rspec-support (~> 3.12.0)
202
- rspec-rails (5.1.2)
203
- actionpack (>= 5.2)
204
- activesupport (>= 5.2)
205
- railties (>= 5.2)
206
- rspec-core (~> 3.10)
207
- rspec-expectations (~> 3.10)
208
- rspec-mocks (~> 3.10)
209
- rspec-support (~> 3.10)
210
- rspec-support (3.12.0)
211
- shellany (0.0.1)
212
- sqlite3 (1.5.4)
213
- mini_portile2 (~> 2.8.0)
214
- thor (1.2.1)
215
- timeout (0.3.0)
216
- tzinfo (2.0.5)
217
- concurrent-ruby (~> 1.0)
218
- websocket-driver (0.7.5)
219
- websocket-extensions (>= 0.1.0)
220
- websocket-extensions (0.1.5)
221
- zeitwerk (2.6.6)
222
-
223
- PLATFORMS
224
- ruby
225
-
226
- DEPENDENCIES
227
- activestorage_saas!
228
- aws-sdk-s3 (~> 1.114)
229
- guard-rspec (~> 4.7)
230
- propshaft
231
- rails (~> 7.0)
232
- rspec-rails (~> 5.1)
233
- sqlite3
234
-
235
- BUNDLED WITH
236
- 2.3.17
data/Guardfile DELETED
@@ -1,42 +0,0 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
- ## Uncomment and set this to only include directories you want to watch
5
- # directories %w(app lib config test spec features) \
6
- # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
-
8
- ## Note: if you are using the `directories` clause above and you are not
9
- ## watching the project directory ('.'), then you will want to move
10
- ## the Guardfile to a watched dir and symlink it back, e.g.
11
- #
12
- # $ mkdir config
13
- # $ mv Guardfile config/
14
- # $ ln -s config/Guardfile .
15
- #
16
- # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
-
18
- # Note: The cmd option is now required due to the increasing number of ways
19
- # rspec may be run, below are examples of the most common uses.
20
- # * bundler: 'bundle exec rspec'
21
- # * bundler binstubs: 'bin/rspec'
22
- # * spring: 'bin/rspec' (This will use spring if running and you have
23
- # installed the spring binstubs per the docs)
24
- # * zeus: 'zeus rspec' (requires the server to be started separately)
25
- # * 'just' rspec: 'rspec'
26
-
27
- guard :rspec, cmd: "bundle exec rspec" do
28
- require "guard/rspec/dsl"
29
- dsl = Guard::RSpec::Dsl.new(self)
30
-
31
- # Feel free to open issues for suggestions and improvements
32
-
33
- # RSpec files
34
- rspec = dsl.rspec
35
- watch(rspec.spec_helper) { rspec.spec_dir }
36
- watch(rspec.spec_support) { rspec.spec_dir }
37
- watch(rspec.spec_files)
38
-
39
- # Ruby files
40
- ruby = dsl.ruby
41
- dsl.watch_spec_files_for(ruby.lib_files)
42
- end
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/setup"
2
-
3
- APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
- load "rails/tasks/engine.rake"
5
-
6
- load "rails/tasks/statistics.rake"
7
-
8
- require "bundler/gem_tasks"
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = "activestorage_saas"
5
- spec.version = "7.0.4"
6
- spec.authors = ["xiaohui"]
7
- spec.email = ["xiaohui@tanmer.com"]
8
-
9
- spec.summary = "Wraps multi-tenant storage services as ActiveStorage service"
10
- spec.description = "Each tenant can set its own storage service, ActiveStorage service can be dynamically loaded."
11
- spec.homepage = "https://github.com/xiaohui-zhangxh/activestorage_saas"
12
- spec.license = "MIT"
13
- spec.required_ruby_version = ">= 2.6.0"
14
-
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = spec.homepage
17
- spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject do |f|
23
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
- end
25
- end
26
-
27
- spec.require_paths = ["lib"]
28
-
29
- spec.add_dependency "activestorage", '>=7.0.3.1', '<= 7.0.4'
30
- end
@@ -1,6 +0,0 @@
1
- Rails.application.configure do
2
- # config.active_storage_saas.service_resolver = ->(blob) { StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service }
3
- # config.active_storage_saas.service_name_converter = ->(controller) { controller.send(:current_tenant).storage_service&.to_service_name }
4
- # config.active_storage_saas.service_name_validator = ->(service_name) { StorageServiceConfiguration.valid_service_name?(service_name) }
5
- # config.active_storage_saas.direct_upload_extra_blob_args = ->(controller) { { tenant: controller.send(:current_tenant) } }
6
- end
@@ -1,4 +0,0 @@
1
- module ActivestorageSaas
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end