Skip to content
Open
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
56 changes: 47 additions & 9 deletions lib/shrine/plugins/mongoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
class Shrine
module Plugins
module Mongoid
VALID_FINALIZE_OPTS = [nil, :before_save, :after_save].freeze

def self.load_dependencies(uploader, *)
uploader.plugin :model
uploader.plugin :_persistence, plugin: self
end

def self.configure(uploader, **opts)
unless VALID_FINALIZE_OPTS.include?(opts[:finalize])
fail ArgumentError, "valid finalize options: #{VALID_FINALIZE_OPTS}"
end

uploader.opts[:mongoid] ||= { validations: true, callbacks: true }
uploader.opts[:mongoid].merge!(opts)
end
Expand Down Expand Up @@ -44,6 +50,10 @@ def included(model)
end
end

define_method :"#{name}_finalize" do
send(:"#{name}_attacher").finalize
end

define_method :reload do |*args|
result = super(*args)
instance_variable_set(:"@#{name}_attacher", nil)
Expand All @@ -69,38 +79,66 @@ def mongoid_validate
end
end

# Calls Attacher#save. Called before model save.
# Calls Attacher#save and finalizes attachment if so configured.
# Called before model save.
def mongoid_before_save
return unless changed?

save
finalize if shrine_class.opts[:mongoid][:finalize] == :before_save
end

# Finalizes attachment and persists changes. Called after model save.
# Finalizes attachment if so configured.
# Makes sense when used with the backgrounding plugin.
# Called after model save.
def mongoid_after_save
return unless changed?

finalize
persist
finalize if shrine_class.opts[:mongoid][:finalize] == :after_save
end

# Deletes attached files. Called after model destroy.
def mongoid_after_destroy
destroy_attached
end

# Saves changes to the model instance, raising exception on validation
# errors. Used by the _persistence plugin.
# Saves changes to the model instance, skipping validation.
# Used by the _persistence plugin.
def mongoid_persist
record.save(validate: false)
end

# Internal only
def _find_root_parent(record)
parent = record._parent
return parent unless parent.embedded?

_find_root_parent(parent)
end

# Internal only
def _copy_record_instance(record)
copy = record.dup
copy.id = record.id
copy
end

# Yields the reloaded record. Used by the _persistence plugin.
def mongoid_reload
record_copy = record.dup
record_copy.id = record.id
unless record.persisted?
return yield record
end

unless record.embedded?
return yield _copy_record_instance(record).reload
end

parent_copy = _copy_record_instance(_find_root_parent(record)).reload
record_copy = parent_copy._children.find do |child|
child.class == record.class && child.id == record.id
end

yield record_copy.reload
yield record_copy
end

# Returns true if the data attribute represents a Hash field. Used by
Expand Down
138 changes: 137 additions & 1 deletion test/mongoid_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@shrine.storages[:cache] = Shrine::Storage::Memory.new
@shrine.storages[:store] = Shrine::Storage::Memory.new

@shrine.plugin :mongoid
@shrine.plugin :mongoid, finalize: :before_save

user_class = Class.new
user_class.include Mongoid::Document
Expand Down Expand Up @@ -547,4 +547,140 @@ def validate
end
end
end

describe "child relations support" do
before do
User = @user.class
Photo = Class.new do
include Mongoid::Document
field :title, type: String
field :image_data, type: Hash
end
Photo.include @shrine::Attachment.new(:image)
end

after do
Object.send(:remove_const, "Photo")
Object.send(:remove_const, "User")
end

describe "nested attributes support" do
describe "for referenced models" do
before do
Photo.store_in collection: "photos"
Photo.belongs_to :user
User.has_many :photos, dependent: :destroy
User.accepts_nested_attributes_for :photos, allow_destroy: true
end

it "stores files for nested models" do
user = User.create!(name: "Moe")
user.update!(photos_attributes: [{ image: fakeio }])
photo = user.reload.photos.first
assert photo.image_data["storage"] == "store"
end

describe "with not yet existing parent" do
it "stores files for nested models" do
user =
User.create!(name: "Moe", photos_attributes: [{ image: fakeio }])
photo = user.reload.photos.first
assert photo.image_data["storage"] == "store"
end
end
end

describe "for embedded (many) models" do
before do
Photo.embedded_in :user
User.embeds_many :photos, cascade_callbacks: true
User.accepts_nested_attributes_for :photos, allow_destroy: true
end

it "stores files for nested models" do
user = User.create!(name: "Jacob")
user.update!(photos_attributes: [{ image: fakeio }])
photo = user.reload.photos.first
assert photo.image_data["storage"] == "store"
end

describe "with not yet existing parent" do
it "stores files for nested models" do
user =
User.create!(name: "Moe", photos_attributes: [{ image: fakeio }])
photo = user.reload.photos.first
assert photo.image_data["storage"] == "store"
end
end
end

describe "for embedded (one) model" do
before do
Photo.embedded_in :user
User.embeds_one :photo, cascade_callbacks: true
User.accepts_nested_attributes_for :photo, allow_destroy: true
end

it "stores files for nested model" do
user = User.create!(name: "Jacob")
user.update!(photo_attributes: { image: fakeio })
photo = user.reload.photo
assert photo.image_data["storage"] == "store"
end

describe "with not yet existing parent" do
it "stores files for nested model" do
user =
User.create!(name: "Moe", photo_attributes: { image: fakeio })
photo = user.reload.photo
assert photo.image_data["storage"] == "store"
end
end
end
end

describe "(embedded)" do
before do
Photo.embedded_in :user
User.embeds_one :photo, cascade_callbacks: true
end

describe "Attacher" do
describe "#atomic_persist" do
it "persists the record" do
photo = Photo.new(user: @user)
attacher = @shrine::Attacher.from_model(photo, :image)

file = attacher.attach(fakeio)
photo.save!

photo.title = "me"
attacher.atomic_persist

photo = @user.reload.photo
attacher = @shrine::Attacher.from_model(photo, :image)

assert_equal "me", photo.title
assert_equal file, attacher.file
end
end

describe "#atomic_promote" do
it "promotes cached file to permanent storage" do
photo = Photo.new(user: @user)
attacher = @shrine::Attacher.from_model(photo, :image)

attacher.attach_cached(fakeio)
photo.save!

attacher.atomic_promote

assert attacher.stored?
attacher.reload
assert attacher.stored?
end
end
end
end
end
end