Skip to content

Commit

Permalink
Add mongoid support for mount_uploadcare_file and mount_uploadcare_fi…
Browse files Browse the repository at this point in the history
…le_group methods.
  • Loading branch information
vipulnsward committed Aug 18, 2024
1 parent eb27313 commit 5ab4236
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based now on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

# Unreleased

* Adds mongoid support for `mount_uploadcare_file` and `mount_uploadcare_file_group` methods.

## 3.4.3 — 2024-06-01

### Added
Expand Down
14 changes: 9 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ gemspec

gem 'http-parser', '~> 1.2', '>= 1.2.3'
gem 'rake', '~> 13.0.6'
gem 'rspec', '~> 3.12'
gem 'rspec-rails', '>= 5.1'
gem 'rubocop', '~> 1.48'
gem 'vcr', '~> 6.1'
gem 'webmock', '~> 3.18'

group :test do
gem 'mongoid', '~> 9', require: false
gem 'rspec', '~> 3.12'
gem 'rspec-rails', '>= 5.1'
gem 'rubocop', '~> 1.48'
gem 'vcr', '~> 6.1'
gem 'webmock', '~> 3.18'
end
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ document.addEventListener('turbo:before-cache', function() {
When you mount either Uploadcare File or Group to an attribute, this attribute is getting wrapped with
a Uploadcare object. This feature adds some useful methods to the attribute.

Note: Supports ActiveRecord, ActiveModel and Mongoid models.

#### Uploadcare File

Say, you have such model in your Rails app:
Expand Down
7 changes: 7 additions & 0 deletions lib/uploadcare/rails/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class Engine < ::Rails::Engine
require 'uploadcare/rails/active_record/mount_uploadcare_file'
require 'uploadcare/rails/active_record/mount_uploadcare_file_group'
end

# Load extensions for mongoid
# Extend mongoid with mount_uploadcare_file and mount_uploadcare_file_group methods
ActiveSupport.on_load :mongoid do
require 'uploadcare/rails/mongoid/mount_uploadcare_file'
require 'uploadcare/rails/mongoid/mount_uploadcare_file_group'
end
end
end
end
Expand Down
66 changes: 66 additions & 0 deletions lib/uploadcare/rails/mongoid/mount_uploadcare_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require 'mongoid'
require 'active_support/concern'
require 'uploadcare/rails/services/id_extractor'
require 'uploadcare/rails/jobs/delete_file_job'
require 'uploadcare/rails/jobs/store_file_job'
require 'uploadcare/rails/objects/file'

module Uploadcare
module Rails
module Mongoid
# A module containing Mongoid extension. Allows using uploadcare file methods in Mongoid models
module MountUploadcareFile
extend ActiveSupport::Concern

def build_uploadcare_file(attribute)
cdn_url = read_attribute(attribute).to_s
return if cdn_url.empty?

uuid = IdExtractor.call(cdn_url)
cache_key = File.build_cache_key(cdn_url)
default_attributes = { cdn_url: cdn_url, uuid: uuid.presence }
file_attributes = ::Rails.cache.read(cache_key).presence || default_attributes
Uploadcare::Rails::File.new(file_attributes)
end

class_methods do
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def mount_uploadcare_file(attribute)
define_method attribute do
build_uploadcare_file(attribute)
end

define_method "uploadcare_store_#{attribute}!" do |store_job = StoreFileJob|
file_uuid = public_send(attribute)&.uuid
return unless file_uuid
return store_job.perform_later(file_uuid) if Uploadcare::Rails.configuration.store_files_async

Uploadcare::FileApi.store_file(file_uuid)
end

define_method "uploadcare_delete_#{attribute}!" do |delete_job = DeleteFileJob|
file_uuid = public_send(attribute)&.uuid
return unless file_uuid
return delete_job.perform_later(file_uuid) if Uploadcare::Rails.configuration.delete_files_async

Uploadcare::FileApi.delete_file(file_uuid)
end

unless Uploadcare::Rails.configuration.do_not_store
set_callback(:save, :after, :"uploadcare_store_#{attribute}!", if: :"#{attribute}_changed?")
end

return unless Uploadcare::Rails.configuration.delete_files_after_destroy

set_callback(:destroy, :after, :"uploadcare_delete_#{attribute}!")
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
end
end
end
end
end

Mongoid::Document.include Uploadcare::Rails::Mongoid::MountUploadcareFile
61 changes: 61 additions & 0 deletions lib/uploadcare/rails/mongoid/mount_uploadcare_file_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require 'mongoid'
require 'active_support/concern'
require 'uploadcare/rails/services/id_extractor'
require 'uploadcare/rails/services/files_count_extractor'
require 'uploadcare/rails/jobs/store_group_job'
require 'uploadcare/rails/objects/group'

module Uploadcare
module Rails
module Mongoid
# A module containing Mongoid extension. Allows to use uploadcare group methods in Rails models
module MountUploadcareFileGroup
extend ActiveSupport::Concern

GROUP_ID_REGEX = /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b~\d+/.freeze

def build_uploadcare_file_group(attribute)
cdn_url = read_attribute(attribute).to_s
return if cdn_url.empty?

group_id = IdExtractor.call(cdn_url, GROUP_ID_REGEX).presence
cache_key = Group.build_cache_key(cdn_url)
files_count = FilesCountExtractor.call(group_id)
default_attributes = { cdn_url: cdn_url, id: group_id, files_count: files_count }
file_attributes = ::Rails.cache.read(cache_key).presence || default_attributes
Uploadcare::Rails::Group.new(file_attributes)
end

class_methods do
# rubocop:disable Metrics/MethodLength
def mount_uploadcare_file_group(attribute)
define_singleton_method "has_uploadcare_file_group_for_#{attribute}?" do
true
end

define_method attribute do
build_uploadcare_file_group attribute
end

define_method "uploadcare_store_#{attribute}!" do |store_job = StoreGroupJob|
group_id = public_send(attribute)&.id
return unless group_id
return store_job.perform_later(group_id) if Uploadcare::Rails.configuration.store_files_async

Uploadcare::GroupApi.store_group(group_id)
end

return if Uploadcare::Rails.configuration.do_not_store

set_callback :save, :after, :"uploadcare_store_#{attribute}!"
end
# rubocop:enable Metrics/MethodLength
end
end
end
end
end

Mongoid::Document.include Uploadcare::Rails::Mongoid::MountUploadcareFileGroup
4 changes: 2 additions & 2 deletions lib/uploadcare/rails/objects/concerns/loadable.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# frozen_string_literal: true

require 'active_record'
require 'active_model'

module Uploadcare
module Rails
module Objects
# A module that contains methods for attribute assignation and caching
module Loadable
extend ActiveSupport::Concern
include ::ActiveRecord::AttributeAssignment
include ActiveModel::AttributeAssignment

class_methods do
def build_cache_key(key)
Expand Down
110 changes: 110 additions & 0 deletions spec/uploadcare/rails/mongoid/mount_uploadcare_file_group_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# frozen_string_literal: true

require 'spec_helper'
require 'uploadcare/rails/mongoid/mount_uploadcare_file_group'

describe Uploadcare::Rails::Mongoid::MountUploadcareFileGroup do
before do
allow(Rails).to receive(:cache).and_return(double(read: nil, write: nil))
allow(Uploadcare::Rails).to receive(:configuration).and_return(
OpenStruct.new(
store_files_async: false,
delete_files_async: false,
do_not_store: false,
delete_files_after_destroy: false,
cache_files: false,
cache_namespace: 'uploadcare'
)
)

stub_const 'TestModel', Class.new
TestModel.class_eval do
include Mongoid::Document
include Uploadcare::Rails::Mongoid::MountUploadcareFileGroup
extend ActiveModel::Callbacks

field :cdn_url, type: String

define_model_callbacks :save, only: :after

mount_uploadcare_file_group :cdn_url
end
end

let(:cdn_url) { 'https://api.uploadcare.com/groups/e6c3fb25-0653-454c-9c8e-7e91902bb044~2/' }
let(:model) { TestModel.new(cdn_url: cdn_url) }
let(:files_count) { 2 }
let(:file_attributes) { { cdn_url: cdn_url, id: group_id, files_count: files_count } }
let(:group_id) { 'e6c3fb25-0653-454c-9c8e-7e91902bb044~2' }

describe '#build_uploadcare_file_group' do
let(:subject) { model.build_uploadcare_file_group(:cdn_url) }

context 'when cdn_url is empty' do
it 'returns nil' do
model.cdn_url = ''
expect(subject).to be_nil
end
end

context 'when cdn_url is not empty' do
it 'returns a new Uploadcare::Rails::Group object' do
expect(subject).to be_a(Uploadcare::Rails::Group)
end

it 'sets the correct attributes on the Uploadcare::Rails::Group object' do
expect(subject.cdn_url).to eq(cdn_url)
expect(subject.id).to eq(group_id)
expect(subject.files_count.to_i).to eq(files_count)
end
end
end

describe 'Singleton .has_uploadcare_file_group_for_cdn_url?' do
it 'returns true' do
expect(TestModel.has_uploadcare_file_group_for_cdn_url?).to be(true)
end
end

describe '.mount_uploadcare_file_group' do
let(:attribute) { :cdn_url }

it 'defines a getter method for the specified attribute' do
expect(model).to respond_to(attribute)
end

it 'defines a method to store the file group asynchronously' do
expect(model).to respond_to("uploadcare_store_#{attribute}!")
end

context 'when the file group is present' do
it 'stores the file group synchronously if not configured for async storage' do
Uploadcare::Rails.configuration.store_files_async = false
expect(Uploadcare::GroupApi).to receive(:store_group).with(group_id)
model.send("uploadcare_store_#{attribute}!")
end

it 'performs the store job asynchronously if configured' do
Uploadcare::Rails.configuration.store_files_async = true
expect(Uploadcare::Rails::StoreGroupJob).to receive(:perform_later).with(group_id)
model.send("uploadcare_store_#{attribute}!")
end
end

context 'when do_not_store configuration is true' do
it 'does not define the after_save callback' do
Uploadcare::Rails.configuration.do_not_store = true
expect(TestModel).not_to receive(:after_save)
TestModel.mount_uploadcare_file_group(:attribute)
end
end

context 'when do_not_store configuration is false' do
it 'defines the after_save callback' do
Uploadcare::Rails.configuration.do_not_store = false
expect(TestModel).to receive(:set_callback).with(:save, :after, :uploadcare_store_cdn_url!)
TestModel.mount_uploadcare_file_group(attribute)
end
end
end
end
Loading

0 comments on commit 5ab4236

Please sign in to comment.