Class: Service::SharepointService
- Inherits:
-
Service
- Object
- Service
- Service::SharepointService
- Defined in:
- lib/active_storage/service/sharepoint_service.rb
Overview
SharePoint Storage Service
Implements the Active Storage service interface to interact with Microsoft 365 SharePoint via the Microsoft Graph API.
Overview
This service allows Rails Active Storage to use Microsoft 365 SharePoint as a file storage backend. It handles all file operations (upload, download, delete, check existence) by communicating with SharePoint via the Microsoft Graph API.
Configuration
Configure in your storage.yml:
# config/storage.yml
sharepoint:
service: Sharepoint
ms_graph_url: https://graph.microsoft.com
ms_graph_version: v1.0
auth_host: https://login.microsoftonline.com
tenant_id: your-tenant-id
app_id: your-app-id
secret: your-client-secret
site_id: your-site-id
drive_id: your-drive-id
Then activate in your environment config:
config.active_storage.service = :sharepoint
Usage
Use normally with Active Storage in your models:
class Document < ApplicationRecord
has_one_attached :file
end
doc = Document.new
doc.file.attach(io: File.open("document.pdf"), filename: "doc.pdf")
doc.file.download # => file contents
doc.file.attached? # => true
Implementation Details
The service implements all required Active Storage methods:
-
upload(key, io) - Upload file to SharePoint
-
download(key) - Download file from SharePoint
-
download_chunk(key, range) - Download partial file content
-
delete(key) - Delete file from SharePoint
-
exist?(key) - Check if file exists
-
url(key) - Get URL for blob
Key Points
-
File keys are mapped to blob filenames for better SharePoint organization
-
Automatic token refresh on 401 responses
-
Redirect following for CDN/Azure Blob Storage downloads
-
Signed URLs prevent 401 issues by routing through authenticated controller
-
Deferred deletion using PendingDelete registry
Error Handling
The service raises StandardError for invalid operations. Common errors:
-
“Failed to upload file to SharePoint” - Upload returned non-success status
-
“Failed to download file from SharePoint” - Download failed with error status
-
“Filename not found for key” - Blob deleted before file deletion from SharePoint
Performance Considerations
-
Tokens are cached and automatically refreshed before expiration
-
Chunked downloads supported for large files
-
Redirects followed to access CDN URLs without authorization issues
Instance Attribute Summary collapse
-
#auth ⇒ Authentication
readonly
Authentication handler.
-
#config ⇒ Configuration
readonly
SharePoint configuration.
-
#http ⇒ Http
readonly
HTTP request handler.
Instance Method Summary collapse
-
#delete(key) ⇒ Boolean
Delete a file from SharePoint.
-
#delete_prefixed(prefix) ⇒ void
Delete files matching a prefix (compatibility method).
-
#download(key) ⇒ String
Download a file from SharePoint.
-
#download_chunk(key, range) ⇒ String
Download a chunk (partial content) of a file from SharePoint.
-
#exist?(key) ⇒ Boolean
Check if a file exists in SharePoint.
-
#fetch_chunk(key, range) ⇒ Net::HTTPResponse
Fetch chunk response from SharePoint using HTTP Range header.
-
#fetch_download(key) ⇒ Net::HTTPResponse
Fetch the raw download response from SharePoint.
-
#handle_download_response(response) ⇒ String
Handle the HTTP response from a download request.
-
#initialize(**options) ⇒ SharepointService
constructor
Initialize the SharePoint storage service.
-
#upload(key, io) ⇒ void
Upload a file to SharePoint.
-
#url(key) ⇒ String
Get the URL for downloading a blob.
Constructor Details
#initialize(**options) ⇒ SharepointService
Initialize the SharePoint storage service
Creates configuration, authentication, and HTTP handler instances. The service is ready to use immediately after initialization.
122 123 124 125 126 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 122 def initialize(**) # rubocop:disable Lint/MissingSuper @config = M365ActiveStorage::Configuration.new(**) @auth = M365ActiveStorage::Authentication.new(@config) @http = M365ActiveStorage::Http.new(@auth) end |
Instance Attribute Details
#auth ⇒ Authentication (readonly)
Authentication handler
95 96 97 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 95 def auth @auth end |
#config ⇒ Configuration (readonly)
SharePoint configuration
95 96 97 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 95 def config @config end |
#http ⇒ Http (readonly)
HTTP request handler
95 96 97 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 95 def http @http end |
Instance Method Details
#delete(key) ⇒ Boolean
Delete a file from SharePoint
Removes a file from the SharePoint drive. Requires the filename to be available from the PendingDelete registry (set before the blob was deleted).
272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 272 def delete(key) auth.ensure_valid_token # get the filename from the pending deletes storage # because once the blob is deleted, we can no longer get the filename from the blob record storage_name = M365ActiveStorage::PendingDelete.get(key) raise "Filename not found for key #{key}. Cannot delete file from SharePoint." unless storage_name delete_url = "#{drive_url}/root:/#{CGI.escape(storage_name)}" response = http.delete(delete_url) response.code.to_i == 204 end |
#delete_prefixed(prefix) ⇒ void
This method returns an undefined value.
Delete files matching a prefix (compatibility method)
Retro compatibility method. Not implemented as the service works with individual keys rather than prefixes. Raises no error to maintain compatibility.
291 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 291 def delete_prefixed(prefix); end |
#download(key) ⇒ String
Download a file from SharePoint
Retrieves the complete file content from SharePoint. Automatically retries once if the token expires (401 response).
168 169 170 171 172 173 174 175 176 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 168 def download(key) response = fetch_download(key) if response.code.to_i == 401 # Token might have expired, force refresh and retry once auth.expire_token! response = fetch_download(key) end handle_download_response(response) end |
#download_chunk(key, range) ⇒ String
Download a chunk (partial content) of a file from SharePoint
Retrieves a specific byte range from a file, useful for large file streaming. Implements HTTP Range requests.
236 237 238 239 240 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 236 def download_chunk(key, range) auth.ensure_valid_token response = fetch_chunk(key, range) handle_download_response(response) end |
#exist?(key) ⇒ Boolean
Check if a file exists in SharePoint
Queries SharePoint to check if a file with the given key exists.
302 303 304 305 306 307 308 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 302 def exist?(key) auth.ensure_valid_token storage_name = get_storage_name(key) check_url = "#{drive_url}/root:/#{CGI.escape(storage_name)}" response = http.get(check_url) response.code.to_i == 200 end |
#fetch_chunk(key, range) ⇒ Net::HTTPResponse
Fetch chunk response from SharePoint using HTTP Range header
Internal method for making the HTTP Range request.
251 252 253 254 255 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 251 def fetch_chunk(key, range) storage_name = get_storage_name(key) download_url = "#{drive_url}/root:/#{CGI.escape(storage_name)}:/content" http.get(download_url, { "Range": "bytes=#{range.begin}-#{range.end}" }) end |
#fetch_download(key) ⇒ Net::HTTPResponse
Fetch the raw download response from SharePoint
Internal method that makes the actual HTTP request for downloading a file.
186 187 188 189 190 191 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 186 def fetch_download(key) auth.ensure_valid_token storage_name = get_storage_name(key) download_url = "#{drive_url}/root:/#{CGI.escape(storage_name)}:/content" http.get(download_url) end |
#handle_download_response(response) ⇒ String
Handle the HTTP response from a download request
Processes the response, following redirects if necessary (e.g., to CDN or Azure Blob Storage). Successful responses return the file content.
208 209 210 211 212 213 214 215 216 217 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 208 def handle_download_response(response) case response.code.to_i when 200, 206 response.body when 302, 301 follow_redirect(response["location"]) else raise "Failed to download file from SharePoint: #{response.code}" end end |
#upload(key, io) ⇒ void
This method returns an undefined value.
Upload a file to SharePoint
Uploads file content to the configured SharePoint drive. The file is stored with the blob’s filename for better organization in SharePoint.
145 146 147 148 149 150 151 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 145 def upload(key, io, **) auth.ensure_valid_token storage_name = get_storage_name(key) upload_url = "#{drive_url}/root:/#{CGI.escape(storage_name)}:/content" response = http.put(upload_url, io.read, { "Content-Type": "application/octet-stream" }) handle_upload_response(response) end |
#url(key) ⇒ String
Get the URL for downloading a blob
Returns a signed URL through the authenticated BlobsController rather than a direct SharePoint URL. This is necessary because direct SharePoint URLs require the Authorization header and would fail in browsers.
The URL includes the blob’s signed ID for security and the filename for reference.
327 328 329 330 331 332 333 334 335 |
# File 'lib/active_storage/service/sharepoint_service.rb', line 327 def url(key, **) # returns the path to the authenticated blob controller as direct sharepoint urls will fail with 401 # since its required the Authorization header to access the file # Find the blob and return the signed blob URL blob = ActiveStorage::Blob.find_by(key: key) # return a path to the authenticated blob controller "/rails/active_storage/blobs/#{blob.signed_id}/#{CGI.escape(blob.filename.to_s)}" end |