Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
simi committed Feb 2, 2013
0 parents commit 06ff0d8
Show file tree
Hide file tree
Showing 194 changed files with 3,454 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--tty
--colour
--format <%= ENV["CI"] ? 'progress' : 'documentation'%>
14 changes: 14 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
source 'https://rubygems.org'

gemspec

gem "rake"
gem "mongoid", github: 'mongoid', branch: "4.0.0-dev"

git "git://github.com/rails/rails.git" do
gem "activemodel"
end

group :test do
gem "rspec", "~> 2.11"
end
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2013 Josef Šimánek

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Mongoid::Paranoia

TODO: Write a gem description

## Installation

Add this line to your application's Gemfile:

gem 'mongoid-paranoia'

And then execute:

$ bundle

Or install it yourself as:

$ gem install mongoid-paranoia

## Usage

TODO: Write usage instructions here

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
13 changes: 13 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require "bundler"
Bundler.setup

require "rspec/core/rake_task"
require "rake"

$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)

RSpec::Core::RakeTask.new("spec") do |spec|
spec.pattern = "spec/**/*_spec.rb"
end

task :default => :spec
1 change: 1 addition & 0 deletions lib/mongoid-paranoia.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require "mongoid/paranoia"
157 changes: 157 additions & 0 deletions lib/mongoid/paranoia.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# encoding: utf-8
require 'mongoid/paranoia/monkey_patches'
module Mongoid

# Include this module to get soft deletion of root level documents.
# This will add a deleted_at field to the +Document+, managed automatically.
# Potentially incompatible with unique indices. (if collisions with deleted items)
#
# @example Make a document paranoid.
# class Person
# include Mongoid::Document
# include Mongoid::Paranoia
# end
module Paranoia
extend ActiveSupport::Concern

included do
field :deleted_at, type: Time
class_attribute :paranoid
self.paranoid = true

default_scope where(deleted_at: nil)
scope :deleted, ne(deleted_at: nil)
end

# Delete the paranoid +Document+ from the database completely. This will
# run the destroy callbacks.
#
# @example Hard destroy the document.
# document.destroy!
#
# @return [ true, false ] If the operation succeeded.
#
# @since 1.0.0
def destroy!
run_callbacks(:destroy) { delete! }
end

# Delete the paranoid +Document+ from the database completely.
#
# @example Hard delete the document.
# document.delete!
#
# @return [ true, false ] If the operation succeeded.
#
# @since 1.0.0
def delete!
Persistence::Operations.remove(self).persist
end

# Delete the +Document+, will set the deleted_at timestamp and not actually
# delete it.
#
# @example Soft remove the document.
# document.remove
#
# @param [ Hash ] options The database options.
#
# @return [ true ] True.
#
# @since 1.0.0
def remove(options = {})
cascade!
time = self.deleted_at = Time.now
paranoid_collection.find(atomic_selector).
update({ "$set" => { paranoid_field => time }})
@destroyed = true
IdentityMap.remove(self)
Threaded.clear_options!
true
end
alias :delete :remove

# Determines if this document is destroyed.
#
# @example Is the document destroyed?
# person.destroyed?
#
# @return [ true, false ] If the document is destroyed.
#
# @since 1.0.0
def destroyed?
(@destroyed ||= false) || !!deleted_at
end
alias :deleted? :destroyed?

# Restores a previously soft-deleted document. Handles this by removing the
# deleted_at flag.
#
# @example Restore the document from deleted state.
# document.restore
#
# @return [ Time ] The time the document had been deleted.
#
# @since 1.0.0
def restore
paranoid_collection.find(atomic_selector).
update({ "$unset" => { paranoid_field => true }})
attributes.delete("deleted_at")
@destroyed = false
true
end

# Returns a string representing the documents's key suitable for use in URLs.
def to_param
new_record? ? nil : to_key.join('-')
end

private

# Get the collection to be used for paranoid operations.
#
# @example Get the paranoid collection.
# document.paranoid_collection
#
# @return [ Collection ] The root collection.
#
# @since 2.3.1
def paranoid_collection
embedded? ? _root.collection : self.collection
end

# Get the field to be used for paranoid operations.
#
# @example Get the paranoid field.
# document.paranoid_field
#
# @return [ String ] The deleted at field.
#
# @since 2.3.1
def paranoid_field
embedded? ? "#{atomic_position}.deleted_at" : "deleted_at"
end
end
end

module Mongoid
module Relations
module Embedded
class Many < Relations::Many
# For use only with Mongoid::Paranoia - will be removed in 4.0.
#
# @example Get the deleted documents from the relation.
# person.paranoid_phones.deleted
#
# @return [ Criteria ] The deleted documents.
#
# @since 3.0.10
def deleted
unscoped.deleted
end
# This class handles the behaviour for a document that embeds many other
# documents within in it as an array.
end
end
end
end
101 changes: 101 additions & 0 deletions lib/mongoid/paranoia/monkey_patches.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
module Mongoid
module Validations
class UniquenessValidator < ActiveModel::EachValidator
# Scope the criteria to the scope options provided.
#
# @api private
#
# @example Scope the criteria.
# validator.scope(criteria, document)
#
# @param [ Criteria ] criteria The criteria to scope.
# @param [ Document ] document The document being validated.
#
# @return [ Criteria ] The scoped criteria.
#
# @since 2.3.0
def scope(criteria, document, attribute)
Array.wrap(options[:scope]).each do |item|
name = document.database_field_name(item)
criteria = criteria.where(item => document.attributes[name])
end
criteria = criteria.where(deleted_at: nil) if document.respond_to?(:paranoid)
criteria
end
end
end
end

module Mongoid
module Relations
module Builders
module NestedAttributes
class Many < NestedBuilder
# Destroy the child document, needs to do some checking for embedded
# relations and delay the destroy in case parent validation fails.
#
# @api private
#
# @example Destroy the child.
# builder.destroy(parent, relation, doc)
#
# @param [ Document ] parent The parent document.
# @param [ Proxy ] relation The relation proxy.
# @param [ Document ] doc The doc to destroy.
#
# @since 3.0.10
def destroy(parent, relation, doc)
doc.flagged_for_destroy = true
if !doc.embedded? || parent.new_record? || doc.paranoid?
destroy_document(relation, doc)
else
parent.flagged_destroys.push(->{ destroy_document(relation, doc) })
end
end
end
end
end
end
end

module Mongoid
module Relations
module Embedded
# This class handles the behaviour for a document that embeds many other
# documents within in it as an array.
class Many < Relations::Many
# Delete the supplied document from the target. This method is proxied
# in order to reindex the array after the operation occurs.
#
# @example Delete the document from the relation.
# person.addresses.delete(address)
#
# @param [ Document ] document The document to be deleted.
#
# @return [ Document, nil ] The deleted document or nil if nothing deleted.
#
# @since 2.0.0.rc.1
def delete(document)
execute_callback :before_remove, document
doc = target.delete_one(document)
if doc && !_binding?
_unscoped.delete_one(doc) unless doc.paranoid?
if _assigning?
if doc.paranoid?
doc.destroy(suppress: true)
else
base.add_atomic_pull(doc)
end
else
doc.delete(suppress: true)
unbind_one(doc)
end
end
reindex
execute_callback :after_remove, document
doc
end
end
end
end
end
21 changes: 21 additions & 0 deletions mongoid-paranoia.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |gem|
gem.name = "mongoid-paranoia"
gem.version = "0.1"
gem.authors = ["Durran Jordan", "Josef Šimánek"]
gem.email = ["durran@gmail.com", "retro@ballgag.cz"]
gem.description = %q{There may be times when you don't want documents to actually get deleted from the database, but "flagged" as deleted. Mongoid provides a Paranoia module to give you just that.}
gem.summary = %q{Paranoid documents}
gem.homepage = "https://github.com/simi/mongoid-paranoia"

gem.files = `git ls-files`.split($/)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]

gem.add_dependency "mongoid", '~> 4.0'
gem.add_development_dependency "rspec", '~> 2.11'
end
Loading

0 comments on commit 06ff0d8

Please sign in to comment.