Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mongoid support for mount_uploadcare_file and mount_uploadcare_file_group methods. #148

Merged
merged 3 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions gemfiles/Gemfile-rails-6-1
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ gem 'rspec-rails', '>= 5.1'
gem 'rubocop', '~> 1.48'
gem 'vcr', '~> 6.1'
gem 'webmock', '~> 3.18'
gem 'mongoid', '~> 9', require: false
1 change: 1 addition & 0 deletions gemfiles/Gemfile-rails-7-0
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ gem 'rspec-rails', '>= 5.1'
gem 'rubocop', '~> 1.48'
gem 'vcr', '~> 6.1'
gem 'webmock', '~> 3.18'
gem 'mongoid', '~> 9', require: false
1 change: 1 addition & 0 deletions gemfiles/Gemfile-rails-7-1
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ gem 'rspec-rails', '>= 5.1'
gem 'rubocop', '~> 1.48'
gem 'vcr', '~> 6.1'
gem 'webmock', '~> 3.18'
gem 'mongoid', '~> 9', require: false
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(
double(
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
allow(Uploadcare::Rails.configuration).to receive(:store_files_async).and_return(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
allow(Uploadcare::Rails.configuration).to receive(:store_files_async).and_return(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
allow(Uploadcare::Rails.configuration).to receive(:do_not_store).and_return(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
allow(Uploadcare::Rails.configuration).to receive(:do_not_store).and_return(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