millstone 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +12 -0
- data/lib/millstone/active_record/associations/belongs_to_association.rb +14 -0
- data/lib/millstone/active_record/associations/belongs_to_polymorphic_association.rb +14 -0
- data/lib/millstone/active_record/associations/has_many_through_without_deleted_association.rb +19 -0
- data/lib/millstone/active_record/associations.rb +50 -0
- data/lib/millstone/active_record/extension.rb +137 -0
- data/lib/millstone/active_record/relation_methods.rb +120 -0
- data/lib/millstone/active_record/validations/uniqueness_with_deleted.rb +49 -0
- data/lib/millstone.rb +2 -0
- data/millstone.gemspec +27 -0
- data/spec/models/associations/belongs_to_association_spec.rb +46 -0
- data/spec/models/associations/belongs_to_polymorphic_association_spec.rb +46 -0
- data/spec/models/associations/has_many_through_without_deleted_association_spec.rb +62 -0
- data/spec/models/extension_spec.rb +55 -0
- data/spec/models/relation_methods_spec.rb +44 -0
- data/spec/models/validations/uniqueness_with_deleted_spec.rb +43 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/examples/check_soft_deletion.rb +6 -0
- data/spec/support/examples/execute_hard_deletion.rb +43 -0
- data/spec/support/examples/execute_soft_deletion.rb +57 -0
- data/spec/support/examples/hide_soft_deletion_marked_record.rb +98 -0
- data/spec/support/migration.rb +49 -0
- metadata +196 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Yoshikazu Ozawa
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Millstone
|
2
|
+
|
3
|
+
ActiveRecord plugin which hides records instead of deleting them for Rails3
|
4
|
+
|
5
|
+
Millstone is extending ActiveRecord::Relation
|
6
|
+
|
7
|
+
## Credits
|
8
|
+
|
9
|
+
This plugin was inspired by [rails3_acts_as_paranoid](https://github.com/goncalossilva/rails3_acts_as_paranoid) and [acts_as_paranoid](http://github.com/technoweenie/acts_as_paranoid).
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
millstone
|
15
|
+
end
|
16
|
+
|
17
|
+
### Options
|
18
|
+
|
19
|
+
- :column => 'deleted_at'
|
20
|
+
- :type => 'time'
|
21
|
+
|
22
|
+
### Filtering
|
23
|
+
|
24
|
+
User.all # retrieves the non-deleted records
|
25
|
+
User.only_deleted # retrieves the deleted records
|
26
|
+
User.with_deleted # retrieves all records, deleted or not
|
27
|
+
|
28
|
+
### Real deletion
|
29
|
+
|
30
|
+
user.destroy!
|
31
|
+
user.delete_all!(conditions)
|
32
|
+
|
33
|
+
### Validation
|
34
|
+
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
acts_as_paranoid
|
37
|
+
validates_uniqueness_of :name
|
38
|
+
end
|
39
|
+
|
40
|
+
User.create(:name => 'foo').destroy
|
41
|
+
User.new(:name => 'foo').valid? #=> true
|
42
|
+
|
43
|
+
|
44
|
+
class User < ActiveRecord::Base
|
45
|
+
acts_as_paranoid
|
46
|
+
validates_uniqueness_of_with_deleted :name
|
47
|
+
end
|
48
|
+
|
49
|
+
User.create(:name => 'foo').destroy
|
50
|
+
User.new(:name => 'foo').valid? #=> false
|
51
|
+
|
52
|
+
### Status
|
53
|
+
|
54
|
+
user = User.create(:name => 'foo')
|
55
|
+
user.deleted? #=> false
|
56
|
+
user.destroy
|
57
|
+
User.with_deleted.first.deleted? #=> true
|
58
|
+
|
59
|
+
### Association options
|
60
|
+
|
61
|
+
class Parent < ActiveRecord::Base
|
62
|
+
acts_as_pranoid
|
63
|
+
has_many :children
|
64
|
+
end
|
65
|
+
|
66
|
+
class Child < ActiveRecord::Base
|
67
|
+
acts_as_paranoid
|
68
|
+
belongs_to :parent
|
69
|
+
end
|
70
|
+
|
71
|
+
parent = Parent.create(:name => "foo")
|
72
|
+
child = parent.children.create!(:name => "bar")
|
73
|
+
parent.destroy
|
74
|
+
child.parent # => nil
|
75
|
+
|
76
|
+
|
77
|
+
class Child < ActiveRecord::Base
|
78
|
+
acts_as_paranoid
|
79
|
+
belongs_to :parent, :with_deleted => true
|
80
|
+
end
|
81
|
+
|
82
|
+
parent = Parent.create(:name => "foo")
|
83
|
+
child = parent.children.create!(:name => "bar")
|
84
|
+
parent.destroy
|
85
|
+
child.parent #=> #<Parent ... deleted_at: deleted_time>
|
86
|
+
child.parent.deleted? #=> true
|
87
|
+
|
88
|
+
Copyright © 2011 Yoshikazu Ozawa, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'bundler'
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
8
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :spec
|
12
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Millstone
|
2
|
+
module ActiveRecord
|
3
|
+
module Associations
|
4
|
+
class BelongsToAssociation < ::ActiveRecord::Associations::BelongsToAssociation
|
5
|
+
private
|
6
|
+
def find_target
|
7
|
+
@reflection.klass.send(:with_scope, :find => {:with_deleted => true}) do
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Millstone
|
2
|
+
module ActiveRecord
|
3
|
+
module Associations
|
4
|
+
class BelongsToPolymorphicAssociation < ::ActiveRecord::Associations::BelongsToPolymorphicAssociation
|
5
|
+
private
|
6
|
+
def find_target
|
7
|
+
association_class.try(:send, :with_scope, :find => {:with_deleted => true}) do
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Millstone
|
2
|
+
module ActiveRecord
|
3
|
+
module Associations
|
4
|
+
class HasManyThroughWithoutDeletedAssociation < ::ActiveRecord::Associations::HasManyThroughAssociation
|
5
|
+
def construct_conditions
|
6
|
+
return super unless @reflection.through_reflection.klass.as_millstone?
|
7
|
+
|
8
|
+
table_name = @reflection.through_reflection.quoted_table_name
|
9
|
+
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
10
|
+
"#{table_name}.#{attr} = #{value}"
|
11
|
+
end
|
12
|
+
conditions << @reflection.through_reflection.klass.millstone_without_deleted_conditions
|
13
|
+
conditions << sql_conditions if sql_conditions
|
14
|
+
"(" + conditions.join(') AND (') + ")"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'millstone/active_record/associations/belongs_to_association'
|
2
|
+
require 'millstone/active_record/associations/belongs_to_polymorphic_association'
|
3
|
+
require 'millstone/active_record/associations/has_many_through_without_deleted_association'
|
4
|
+
|
5
|
+
module Millstone
|
6
|
+
module ActiveRecord
|
7
|
+
module Associations
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class << self
|
12
|
+
alias_method_chain :has_many, :millstone
|
13
|
+
alias_method_chain :belongs_to, :millstone
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def has_many_with_millstone(association_id, options = {}, &extension)
|
19
|
+
with_deleted = options.delete :with_deleted
|
20
|
+
has_many_without_millstone(association_id, options, &extension).tap do
|
21
|
+
reflection = reflect_on_association(association_id)
|
22
|
+
|
23
|
+
if options[:through] and !with_deleted
|
24
|
+
collection_accessor_methods(reflection, Millstone::ActiveRecord::Associations::HasManyThroughWithoutDeletedAssociation)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def belongs_to_with_millstone(association_id, options = {})
|
30
|
+
with_deleted = options.delete :with_deleted
|
31
|
+
belongs_to_without_millstone(association_id, options).tap do
|
32
|
+
if with_deleted
|
33
|
+
reflection = reflect_on_association(association_id)
|
34
|
+
|
35
|
+
if reflection.options[:polymorphic]
|
36
|
+
association_accessor_methods(reflection, Millstone::ActiveRecord::Associations::BelongsToPolymorphicAssociation)
|
37
|
+
else
|
38
|
+
association_accessor_methods(reflection, Millstone::ActiveRecord::Associations::BelongsToAssociation)
|
39
|
+
association_constructor_method(:build, reflection, Millstone::ActiveRecord::Associations::BelongsToAssociation)
|
40
|
+
association_constructor_method(:create, reflection, Millstone::ActiveRecord::Associations::BelongsToAssociation)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
ActiveRecord::Base.send :include, Millstone::ActiveRecord::Associations
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'millstone/active_record/relation_methods'
|
2
|
+
require 'millstone/active_record/validations/uniqueness_with_deleted'
|
3
|
+
|
4
|
+
module Millstone
|
5
|
+
module ActiveRecord
|
6
|
+
class AlreadyMarkedDeletion < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
module Extension
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
class << self
|
14
|
+
# for acts_as_paranoid
|
15
|
+
alias_method :acts_as_paranoid, :millstone
|
16
|
+
alias_method :paranoid?, :as_millstone?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def as_millstone?
|
22
|
+
self.included_modules.include?(InstanceMethods)
|
23
|
+
end
|
24
|
+
|
25
|
+
def millstone(options = {})
|
26
|
+
options = options.reverse_merge(:column => :deleted_at, :type => :time)
|
27
|
+
|
28
|
+
unless [:time, :boolean].include? options[:type]
|
29
|
+
raise ArgumentError, "'time' or 'boolean' expected for :type option, got #{options[:type]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
class_attribute :millstone_configuration, :millstone_column_reference
|
33
|
+
self.millstone_configuration = options
|
34
|
+
self.millstone_column_reference = "#{self.table_name}.#{millstone_configuration[:column]}"
|
35
|
+
|
36
|
+
return if as_millstone?
|
37
|
+
|
38
|
+
extend ClassMethods
|
39
|
+
include InstanceMethods
|
40
|
+
include Validations
|
41
|
+
|
42
|
+
self.class_eval do
|
43
|
+
alias_method :paranoid_value, :millstone_column_value
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
delegate :destroy!, :destroy_all!, :delete!, :delete_all!, :to => :scoped
|
48
|
+
delegate :with_deleted, :only_deleted, :to => :scoped
|
49
|
+
alias_method_chain :relation, :millstone
|
50
|
+
|
51
|
+
# for acts_as_paranoid
|
52
|
+
alias_method :paranoid_column, :millstone_column
|
53
|
+
alias_method :paranoid_column_type, :millstone_type
|
54
|
+
alias_method :paranoid_column_reference, :millstone_column_reference
|
55
|
+
alias_method :paranoid_configuration, :millstone_configuration
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
def millstone_column
|
61
|
+
millstone_configuration[:column].to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
def millstone_type
|
65
|
+
millstone_configuration[:type].to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
def millstone_generate_column_value
|
69
|
+
case millstone_type
|
70
|
+
when :time then Time.now
|
71
|
+
when :boolean then true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def millstone_without_deleted_conditions
|
76
|
+
sanitize_sql(["#{millstone_column_reference} IS ?", nil])
|
77
|
+
end
|
78
|
+
|
79
|
+
def millstone_only_deleted_conditions
|
80
|
+
sanitize_sql(["#{millstone_column_reference} IS NOT ?", nil])
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def relation_with_millstone
|
85
|
+
relation = relation_without_millstone
|
86
|
+
relation.extending Millstone::ActiveRecord::RelationMethods
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module InstanceMethods
|
91
|
+
def destroy
|
92
|
+
with_transaction_returning_status do
|
93
|
+
_run_destroy_callbacks do
|
94
|
+
raise AlreadyMarkedDeletion, "#{self.class.name} ID=#{self.id} already marked deletion." unless self.millstone_column_value.nil?
|
95
|
+
self.class.delete(self.id)
|
96
|
+
self.millstone_column_value = self.class.millstone_generate_column_value
|
97
|
+
freeze
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy!
|
103
|
+
with_transaction_returning_status do
|
104
|
+
_run_destroy_callbacks do
|
105
|
+
self.class.delete!(self.id)
|
106
|
+
freeze
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def recover(options = {})
|
112
|
+
raise "Millstone is not support"
|
113
|
+
end
|
114
|
+
|
115
|
+
def recover_dependent_associations(window, options = {})
|
116
|
+
raise "Millstone is not support"
|
117
|
+
end
|
118
|
+
|
119
|
+
def deleted?
|
120
|
+
!millstone_column_value.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
def millstone_column_value
|
124
|
+
self.send(self.class.millstone_column)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def millstone_column_value=(value)
|
129
|
+
self.send("#{self.class.millstone_column}=", value)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
ActiveRecord::Base.send :include, Millstone::ActiveRecord::Extension
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Millstone
|
2
|
+
module ActiveRecord
|
3
|
+
module RelationMethods
|
4
|
+
def self.extended(base)
|
5
|
+
base.class_eval do
|
6
|
+
attr_accessor :with_deleted_value, :only_deleted_value
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def destroy_all!(conditions = nil)
|
11
|
+
if with_deleted_value.nil? and only_deleted_value.nil?
|
12
|
+
with_deleted.destroy_all!(conditions)
|
13
|
+
elsif conditions
|
14
|
+
where(conditions).destroy_all!
|
15
|
+
else
|
16
|
+
to_a.each {|object| object.destroy! }.tap { reset }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy!(id)
|
21
|
+
if with_deleted_value.nil? and only_deleted_value.nil?
|
22
|
+
with_deleted.destroy!(id)
|
23
|
+
elsif id.is_a?(Array)
|
24
|
+
id.map {|one_id| destroy!(one_id) }
|
25
|
+
else
|
26
|
+
find(id).destroy!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_all(conditions = nil)
|
31
|
+
conditions ? where(conditions).delete_all : update_all(@klass.millstone_column => @klass.millstone_generate_column_value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_all!(conditions = nil)
|
35
|
+
if with_deleted_value.nil? and only_deleted_value.nil?
|
36
|
+
with_deleted.delete_all!(conditions)
|
37
|
+
elsif conditions
|
38
|
+
where(conditions).delete_all!
|
39
|
+
else
|
40
|
+
arel.delete.tap { reset }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete(id_or_array)
|
45
|
+
where(@klass.primary_key => id_or_array).update_all(@klass.millstone_column => @klass.millstone_generate_column_value)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete!(id_or_array)
|
49
|
+
where(@klass.primary_key => id_or_array).delete_all!
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_deleted(with = true)
|
53
|
+
relation = clone
|
54
|
+
relation.with_deleted_value = with
|
55
|
+
relation
|
56
|
+
end
|
57
|
+
|
58
|
+
def only_deleted(only = true)
|
59
|
+
relation = clone
|
60
|
+
relation.only_deleted_value = only
|
61
|
+
relation
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge(r)
|
65
|
+
merged_relation = super
|
66
|
+
[:with_deleted, :only_deleted].each do |method|
|
67
|
+
value = r.send(:"#{method}_value")
|
68
|
+
merged_relation.send(:"#{method}_value=", value) unless value.nil?
|
69
|
+
end
|
70
|
+
merged_relation
|
71
|
+
end
|
72
|
+
|
73
|
+
def except(*skips)
|
74
|
+
result = super
|
75
|
+
([:with_deleted, :only_deleted] - skips).each do |method|
|
76
|
+
result.send(:"#{method}_value=", send(:"#{method}_value"))
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
def only(*onlies)
|
82
|
+
result = super
|
83
|
+
([:with_deleted, :only_deleted] & onlies).each do |method|
|
84
|
+
result.send(:"#{method}_value=", send(:"#{method}_value"))
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
def apply_finder_options(options)
|
90
|
+
return clone unless options
|
91
|
+
|
92
|
+
finders = options.dup
|
93
|
+
with_deleted = finders.delete(:with_deleted)
|
94
|
+
only_deleted = finders.delete(:only_deleted)
|
95
|
+
|
96
|
+
relation = super(finders)
|
97
|
+
relation = relation.with_deleted(with_deleted) unless with_deleted.nil?
|
98
|
+
relation = relation.only_deleted(only_deleted) unless only_deleted.nil?
|
99
|
+
|
100
|
+
relation
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def build_arel
|
105
|
+
arel = super
|
106
|
+
return arel unless @klass.as_millstone?
|
107
|
+
|
108
|
+
if @with_deleted_value
|
109
|
+
# nothing
|
110
|
+
elsif @only_deleted_value
|
111
|
+
arel = collapse_wheres(arel, [@klass.millstone_only_deleted_conditions])
|
112
|
+
else
|
113
|
+
arel = collapse_wheres(arel, [@klass.millstone_without_deleted_conditions])
|
114
|
+
end
|
115
|
+
|
116
|
+
arel
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
3
|
+
module Millstone
|
4
|
+
module ActiveRecord
|
5
|
+
module Validations
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
end
|
10
|
+
|
11
|
+
class UniquenessWithDeletedValidator < ::ActiveRecord::Validations::UniquenessValidator
|
12
|
+
def validate_each(record, attribute, value)
|
13
|
+
finder_class = find_finder_class_for(record)
|
14
|
+
table = finder_class.unscoped
|
15
|
+
|
16
|
+
table_name = record.class.quoted_table_name
|
17
|
+
|
18
|
+
if value && record.class.serialized_attributes.key?(attribute.to_s)
|
19
|
+
value = YAML.dump value
|
20
|
+
end
|
21
|
+
|
22
|
+
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
|
23
|
+
|
24
|
+
relation = table.with_deleted.where(sql, *params) # find with deleted
|
25
|
+
|
26
|
+
Array.wrap(options[:scope]).each do |scope_item|
|
27
|
+
scope_value = record.send(scope_item)
|
28
|
+
relation = relation.where(scope_item => scope_value)
|
29
|
+
end
|
30
|
+
|
31
|
+
unless record.new_record?
|
32
|
+
# TODO : This should be in Arel
|
33
|
+
relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
|
34
|
+
end
|
35
|
+
|
36
|
+
if relation.exists?
|
37
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def validates_uniqueness_of_with_deleted(*attr_names)
|
44
|
+
validates_with UniquenessWithDeletedValidator, _merge_attributes(attr_names)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/millstone.rb
ADDED
data/millstone.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'millstone'
|
6
|
+
s.version = '0.0.2'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ['Yoshikazu Ozawa']
|
9
|
+
s.email = ['yoshikazu.ozawa@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/relax4u/millstone'
|
11
|
+
s.summary = 'ActiveRecord plugin which allows you to hide without actually deleting them for Rails3.'
|
12
|
+
s.description = 'ActiveRecord plugin which allows you to hide without actually deleting them for Rails3. Millstone is extending ActiveRecord::Relation'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.licenses = ['MIT']
|
20
|
+
|
21
|
+
s.add_dependency 'rails', ['>= 3.0.6']
|
22
|
+
s.add_development_dependency 'bundler', ['>= 1.0.0']
|
23
|
+
s.add_development_dependency 'sqlite3', ['>= 0']
|
24
|
+
s.add_development_dependency 'rspec', ['>= 2.0.0']
|
25
|
+
s.add_development_dependency 'rspec-rails', ['>= 2.0.0']
|
26
|
+
s.add_development_dependency 'delorean', ['>= 0']
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ParentHasChild < ActiveRecord::Base
|
4
|
+
set_table_name 'time_columns'
|
5
|
+
millstone
|
6
|
+
has_one :child, :class_name => 'Child', :foreign_key => 'parent_id'
|
7
|
+
has_one :child_with_millstone, :class_name => 'ChildWithDeleted', :foreign_key => 'parent_id'
|
8
|
+
end
|
9
|
+
|
10
|
+
class Child < ActiveRecord::Base
|
11
|
+
set_table_name 'children'
|
12
|
+
belongs_to :parent, :class_name => 'ParentHasChild', :foreign_key => 'parent_id'
|
13
|
+
end
|
14
|
+
|
15
|
+
class ChildWithDeleted < ActiveRecord::Base
|
16
|
+
set_table_name 'children'
|
17
|
+
belongs_to :parent, :class_name => 'ParentHasChild', :foreign_key => 'parent_id', :with_deleted => true
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Millstone::ActiveRecord::Associations::BelongsToAssociation do
|
21
|
+
describe ActiveRecord::Base do
|
22
|
+
let(:parent) { ParentHasChild.create!(:context => "parent") }
|
23
|
+
|
24
|
+
describe ".belongs_to", "with_deleted => false (default)" do
|
25
|
+
let(:record) { parent.create_child(:context => "child") }
|
26
|
+
let(:record_which_destroyed_parent) do
|
27
|
+
record.parent.destroy
|
28
|
+
record.reload
|
29
|
+
end
|
30
|
+
|
31
|
+
specify { record.parent.should eq parent }
|
32
|
+
specify { record_which_destroyed_parent.parent.should be_nil }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ".belongs_to", "with_deleted => true" do
|
36
|
+
let(:record) { parent.create_child_with_millstone(:context => "child") }
|
37
|
+
let(:record_which_destroyed_parent) do
|
38
|
+
record.parent.destroy
|
39
|
+
record.reload
|
40
|
+
end
|
41
|
+
|
42
|
+
specify { record.parent.should eq parent }
|
43
|
+
specify { record_which_destroyed_parent.parent.should eq parent }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ParentHasPolymorphicChild < ActiveRecord::Base
|
4
|
+
set_table_name 'time_columns'
|
5
|
+
millstone
|
6
|
+
has_one :child, :class_name => 'PolymorphicChild', :as => :polymorphous
|
7
|
+
has_one :child_with_millstone, :class_name => 'PolymorphicChildWithDeleted', :as => :polymorphous
|
8
|
+
end
|
9
|
+
|
10
|
+
class PolymorphicChild < ActiveRecord::Base
|
11
|
+
set_table_name 'polymorphic_children'
|
12
|
+
belongs_to :polymorphous, :polymorphic => true
|
13
|
+
end
|
14
|
+
|
15
|
+
class PolymorphicChildWithDeleted < ActiveRecord::Base
|
16
|
+
set_table_name 'polymorphic_children'
|
17
|
+
belongs_to :polymorphous, :polymorphic => true, :with_deleted => true
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Millstone::ActiveRecord::Associations::BelongsToPolymorphicAssociation do
|
21
|
+
describe ActiveRecord::Base do
|
22
|
+
let(:parent) { ParentHasPolymorphicChild.create!(:context => "parent") }
|
23
|
+
|
24
|
+
describe ".belongs_to", "with_deleted => false (default)" do
|
25
|
+
let(:record) { parent.create_child(:context => "child") }
|
26
|
+
let(:record_which_destroyed_parent) do
|
27
|
+
record.polymorphous.destroy
|
28
|
+
record.reload
|
29
|
+
end
|
30
|
+
|
31
|
+
specify { record.polymorphous.should eq parent }
|
32
|
+
specify { record_which_destroyed_parent.polymorphous.should be_nil }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ".belongs_to", "with_deleted => true" do
|
36
|
+
let(:record) { parent.create_child_with_millstone(:context => "child") }
|
37
|
+
let(:record_which_destroyed_parent) do
|
38
|
+
record.polymorphous.destroy
|
39
|
+
record.reload
|
40
|
+
end
|
41
|
+
|
42
|
+
specify { record.polymorphous.should eq parent }
|
43
|
+
specify { record_which_destroyed_parent.polymorphous.should eq parent }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|