mongoid_paranoia 0.1.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.
- checksums.yaml +15 -0
- data/LICENSE +22 -0
- data/README.md +110 -0
- data/lib/mongoid-paranoia.rb +1 -0
- data/lib/mongoid/paranoia.rb +154 -0
- data/lib/mongoid/paranoia/monkey_patches.rb +113 -0
- data/lib/mongoid/paranoia/version.rb +5 -0
- data/lib/mongoid_paranoia.rb +1 -0
- data/perf/scope.rb +64 -0
- data/spec/app/models/address.rb +71 -0
- data/spec/app/models/appointment.rb +7 -0
- data/spec/app/models/author.rb +6 -0
- data/spec/app/models/fish.rb +8 -0
- data/spec/app/models/paranoid_phone.rb +25 -0
- data/spec/app/models/paranoid_post.rb +53 -0
- data/spec/app/models/person.rb +190 -0
- data/spec/app/models/phone.rb +11 -0
- data/spec/app/models/tag.rb +6 -0
- data/spec/app/models/title.rb +4 -0
- data/spec/mongoid/document_spec.rb +21 -0
- data/spec/mongoid/nested_attributes_spec.rb +164 -0
- data/spec/mongoid/paranoia_spec.rb +751 -0
- data/spec/mongoid/scoping_spec.rb +55 -0
- data/spec/mongoid/validatable/uniqueness_spec.rb +74 -0
- data/spec/spec_helper.rb +80 -0
- metadata +104 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
class Address
|
2
|
+
include Mongoid::Document
|
3
|
+
|
4
|
+
field :_id, type: String, default: ->{ street.try(:parameterize) }
|
5
|
+
|
6
|
+
attr_accessor :mode
|
7
|
+
|
8
|
+
field :address_type
|
9
|
+
field :number, type: Integer
|
10
|
+
field :street
|
11
|
+
field :city
|
12
|
+
field :state
|
13
|
+
field :post_code
|
14
|
+
field :parent_title
|
15
|
+
field :services, type: Array
|
16
|
+
field :latlng, type: Array
|
17
|
+
field :map, type: Hash
|
18
|
+
field :move_in, type: DateTime
|
19
|
+
field :s, type: String, as: :suite
|
20
|
+
field :name, localize: true
|
21
|
+
|
22
|
+
embeds_one :code, validate: false
|
23
|
+
embeds_one :target, as: :targetable, validate: false
|
24
|
+
|
25
|
+
embedded_in :addressable, polymorphic: true do
|
26
|
+
def extension
|
27
|
+
"Testing"
|
28
|
+
end
|
29
|
+
def doctor?
|
30
|
+
title == "Dr"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
accepts_nested_attributes_for :code, :target
|
35
|
+
|
36
|
+
belongs_to :account
|
37
|
+
|
38
|
+
scope :without_postcode, -> {where(postcode: nil)}
|
39
|
+
scope :rodeo, -> {
|
40
|
+
where(street: "Rodeo Dr") do
|
41
|
+
def mansion?
|
42
|
+
all? { |address| address.street == "Rodeo Dr" }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
validates_presence_of :street, on: :update
|
48
|
+
validates_format_of :street, with: /\D/, allow_nil: true
|
49
|
+
|
50
|
+
def set_parent=(set = false)
|
51
|
+
self.parent_title = addressable.title if set
|
52
|
+
end
|
53
|
+
|
54
|
+
def <=>(other)
|
55
|
+
street <=> other.street
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def california
|
60
|
+
where(state: "CA")
|
61
|
+
end
|
62
|
+
|
63
|
+
def homes
|
64
|
+
where(address_type: "Home")
|
65
|
+
end
|
66
|
+
|
67
|
+
def streets
|
68
|
+
all.map(&:street)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class ParanoidPhone
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Paranoia
|
4
|
+
|
5
|
+
attr_accessor :after_destroy_called, :before_destroy_called
|
6
|
+
|
7
|
+
field :number, type: String
|
8
|
+
|
9
|
+
embedded_in :person
|
10
|
+
|
11
|
+
before_destroy :before_destroy_stub, :halt_me
|
12
|
+
after_destroy :after_destroy_stub
|
13
|
+
|
14
|
+
def before_destroy_stub
|
15
|
+
self.before_destroy_called = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_destroy_stub
|
19
|
+
self.after_destroy_called = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def halt_me
|
23
|
+
person.age == 42 ? false : true
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class ParanoidPost
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Paranoia
|
4
|
+
|
5
|
+
field :title, type: String
|
6
|
+
|
7
|
+
attr_accessor :after_destroy_called, :before_destroy_called,
|
8
|
+
:after_restore_called, :before_restore_called,
|
9
|
+
:around_before_restore_called, :around_after_restore_called
|
10
|
+
|
11
|
+
belongs_to :person
|
12
|
+
|
13
|
+
has_and_belongs_to_many :tags
|
14
|
+
has_many :authors, dependent: :delete, inverse_of: :post
|
15
|
+
has_many :titles, dependent: :restrict
|
16
|
+
|
17
|
+
scope :recent, -> {where(created_at: { "$lt" => Time.now, "$gt" => 30.days.ago })}
|
18
|
+
|
19
|
+
before_destroy :before_destroy_stub
|
20
|
+
after_destroy :after_destroy_stub
|
21
|
+
|
22
|
+
before_restore :before_restore_stub
|
23
|
+
after_restore :after_restore_stub
|
24
|
+
around_restore :around_restore_stub
|
25
|
+
|
26
|
+
def before_destroy_stub
|
27
|
+
self.before_destroy_called = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def after_destroy_stub
|
31
|
+
self.after_destroy_called = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def before_restore_stub
|
35
|
+
self.before_restore_called = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def after_restore_stub
|
39
|
+
self.after_restore_called = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def around_restore_stub
|
43
|
+
self.around_before_restore_called = true
|
44
|
+
yield
|
45
|
+
self.around_after_restore_called = true
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def old
|
50
|
+
where(created_at: { "$lt" => 30.days.ago })
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
class Person
|
2
|
+
include Mongoid::Document
|
3
|
+
attr_accessor :mode
|
4
|
+
|
5
|
+
class_attribute :somebody_elses_important_class_options
|
6
|
+
self.somebody_elses_important_class_options = { keep_me_around: true }
|
7
|
+
|
8
|
+
field :title
|
9
|
+
field :terms, type: Boolean
|
10
|
+
field :pets, type: Boolean, default: false
|
11
|
+
field :age, type: Integer, default: "100"
|
12
|
+
field :dob, type: Date
|
13
|
+
field :employer_id
|
14
|
+
field :lunch_time, type: Time
|
15
|
+
field :aliases, type: Array
|
16
|
+
field :map, type: Hash
|
17
|
+
field :map_with_default, type: Hash, default: {}
|
18
|
+
field :score, type: Integer
|
19
|
+
field :blood_alcohol_content, type: Float, default: ->{ 0.0 }
|
20
|
+
field :last_drink_taken_at, type: Date
|
21
|
+
field :ssn
|
22
|
+
field :owner_id, type: Integer
|
23
|
+
field :security_code
|
24
|
+
field :reading, type: Object
|
25
|
+
field :bson_id, type: BSON::ObjectId
|
26
|
+
field :pattern, type: Regexp
|
27
|
+
field :override_me, type: Integer
|
28
|
+
field :t, as: :test, type: String
|
29
|
+
field :i, as: :inte, type: Integer
|
30
|
+
field :a, as: :array, type: Array
|
31
|
+
field :desc, localize: true
|
32
|
+
|
33
|
+
index age: 1
|
34
|
+
index addresses: 1
|
35
|
+
index dob: 1
|
36
|
+
index name: 1
|
37
|
+
index title: 1
|
38
|
+
|
39
|
+
index({ ssn: 1 }, { unique: true })
|
40
|
+
|
41
|
+
attr_reader :rescored
|
42
|
+
|
43
|
+
embeds_many :phone_numbers, class_name: "Phone", validate: false
|
44
|
+
embeds_many :phones, store_as: :mobile_phones, validate: false
|
45
|
+
embeds_many :addresses, as: :addressable, validate: false do
|
46
|
+
def extension
|
47
|
+
"Testing"
|
48
|
+
end
|
49
|
+
def find_by_street(street)
|
50
|
+
where(street: street).first
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
embeds_many :appointments, validate: false
|
55
|
+
embeds_many :paranoid_phones, validate: false
|
56
|
+
|
57
|
+
embeds_one :passport, autobuild: true, store_as: :pass, validate: false
|
58
|
+
embeds_one :pet, class_name: "Animal", validate: false
|
59
|
+
embeds_one :name, as: :namable, validate: false do
|
60
|
+
def extension
|
61
|
+
"Testing"
|
62
|
+
end
|
63
|
+
def dawkins?
|
64
|
+
first_name == "Richard" && last_name == "Dawkins"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
embeds_one :quiz, validate: false
|
68
|
+
|
69
|
+
has_one :game, dependent: :destroy, validate: false do
|
70
|
+
def extension
|
71
|
+
"Testing"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
has_many \
|
76
|
+
:posts,
|
77
|
+
dependent: :delete,
|
78
|
+
validate: false do
|
79
|
+
def extension
|
80
|
+
"Testing"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
has_many :ordered_posts, order: :rating.desc, validate: false
|
84
|
+
has_and_belongs_to_many \
|
85
|
+
:preferences,
|
86
|
+
index: true,
|
87
|
+
dependent: :nullify,
|
88
|
+
validate: false
|
89
|
+
has_and_belongs_to_many :user_accounts, validate: false
|
90
|
+
has_and_belongs_to_many :houses, validate: false
|
91
|
+
has_and_belongs_to_many :ordered_preferences, order: :value.desc, validate: false
|
92
|
+
|
93
|
+
has_many :drugs, validate: false
|
94
|
+
has_many :paranoid_posts, validate: false
|
95
|
+
has_one :account, validate: false
|
96
|
+
has_one :cat, dependent: :nullify, validate: false
|
97
|
+
has_one :book, autobuild: true, validate: false
|
98
|
+
has_one :home, dependent: :delete, validate: false
|
99
|
+
|
100
|
+
belongs_to :paranoid_post
|
101
|
+
|
102
|
+
has_and_belongs_to_many \
|
103
|
+
:administrated_events,
|
104
|
+
class_name: 'Event',
|
105
|
+
inverse_of: :administrators,
|
106
|
+
dependent: :nullify,
|
107
|
+
validate: false
|
108
|
+
|
109
|
+
accepts_nested_attributes_for :addresses
|
110
|
+
accepts_nested_attributes_for :name, update_only: true
|
111
|
+
accepts_nested_attributes_for :pet, allow_destroy: true
|
112
|
+
accepts_nested_attributes_for :game, allow_destroy: true
|
113
|
+
accepts_nested_attributes_for :posts
|
114
|
+
accepts_nested_attributes_for :preferences
|
115
|
+
accepts_nested_attributes_for :quiz
|
116
|
+
accepts_nested_attributes_for :paranoid_phones
|
117
|
+
|
118
|
+
scope :minor, ->{where(:age.lt => 18)}
|
119
|
+
scope :without_ssn, ->{without(:ssn)}
|
120
|
+
scope :search, ->(query){ any_of({ title: query }) }
|
121
|
+
|
122
|
+
def score_with_rescoring=(score)
|
123
|
+
@rescored = score.to_i + 20
|
124
|
+
self.score_without_rescoring = score
|
125
|
+
end
|
126
|
+
|
127
|
+
alias_method_chain :score=, :rescoring
|
128
|
+
|
129
|
+
def update_addresses
|
130
|
+
addresses.each do |address|
|
131
|
+
address.street = "Updated Address"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def employer=(emp)
|
136
|
+
self.employer_id = emp.id
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_addresses=(addresses)
|
140
|
+
self.addresses = addresses
|
141
|
+
end
|
142
|
+
|
143
|
+
def override_me
|
144
|
+
read_attribute(:override_me).to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
class << self
|
148
|
+
def accepted
|
149
|
+
scoped.where(terms: true)
|
150
|
+
end
|
151
|
+
def knight
|
152
|
+
scoped.where(title: "Sir")
|
153
|
+
end
|
154
|
+
def old
|
155
|
+
scoped.where(age: { "$gt" => 50 })
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def reject_if_city_is_empty(attrs)
|
160
|
+
attrs[:city].blank?
|
161
|
+
end
|
162
|
+
|
163
|
+
def reject_if_name_is_blank(attrs)
|
164
|
+
attrs[:first_name].blank?
|
165
|
+
end
|
166
|
+
|
167
|
+
def foo
|
168
|
+
'i_am_foo'
|
169
|
+
end
|
170
|
+
|
171
|
+
def preference_names=(names)
|
172
|
+
names.split(",").each do |name|
|
173
|
+
preference = Preference.where(name: name).first
|
174
|
+
if preference
|
175
|
+
self.preferences << preference
|
176
|
+
else
|
177
|
+
preferences.build(name: name)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def set_on_map_with_default=(value)
|
183
|
+
self.map_with_default["key"] = value
|
184
|
+
end
|
185
|
+
|
186
|
+
reset_callbacks(:validate)
|
187
|
+
reset_callbacks(:create)
|
188
|
+
reset_callbacks(:save)
|
189
|
+
reset_callbacks(:destroy)
|
190
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Mongoid::Document do
|
4
|
+
|
5
|
+
describe ".paranoid?" do
|
6
|
+
|
7
|
+
context "when Mongoid::Paranoia is included" do
|
8
|
+
subject { ParanoidPost }
|
9
|
+
it "returns true" do
|
10
|
+
expect(subject.paranoid?).to be_truthy
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when Mongoid::Paranoia not included" do
|
15
|
+
subject { Author }
|
16
|
+
it "returns true" do
|
17
|
+
expect(subject.paranoid?).to be_falsey
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Mongoid::Attributes::Nested do
|
4
|
+
describe "##{name}_attributes=" do
|
5
|
+
context "when the parent document is new" do
|
6
|
+
context "when the relation is an embeds many" do
|
7
|
+
context "when ids are passed" do
|
8
|
+
|
9
|
+
let(:person) do
|
10
|
+
Person.create
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:address_one) do
|
14
|
+
Address.new(street: "Unter den Linden")
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:address_two) do
|
18
|
+
Address.new(street: "Kurfeurstendamm")
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:phone_one) do
|
22
|
+
ParanoidPhone.new(number: "1")
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:phone_two) do
|
26
|
+
ParanoidPhone.new(number: "2")
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
person.addresses << [ address_one, address_two ]
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when destroy attributes are passed" do
|
34
|
+
context "when the ids match" do
|
35
|
+
context "when allow_destroy is true" do
|
36
|
+
context "when the child is paranoid" do
|
37
|
+
|
38
|
+
before(:all) do
|
39
|
+
Person.send(:undef_method, :paranoid_phones_attributes=)
|
40
|
+
Person.accepts_nested_attributes_for :paranoid_phones,
|
41
|
+
allow_destroy: true
|
42
|
+
end
|
43
|
+
|
44
|
+
after(:all) do
|
45
|
+
Person.send(:undef_method, :paranoid_phones_attributes=)
|
46
|
+
Person.accepts_nested_attributes_for :paranoid_phones
|
47
|
+
end
|
48
|
+
|
49
|
+
[ 1, "1", true, "true" ].each do |truth|
|
50
|
+
|
51
|
+
context "when passed a #{truth} with destroy" do
|
52
|
+
context "when the parent is persisted" do
|
53
|
+
|
54
|
+
let!(:persisted) do
|
55
|
+
Person.create do |p|
|
56
|
+
p.paranoid_phones << [ phone_one, phone_two ]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when setting, pulling, and pushing in one op" do
|
61
|
+
|
62
|
+
before do
|
63
|
+
persisted.paranoid_phones_attributes =
|
64
|
+
{
|
65
|
+
"bar" => { "id" => phone_one.id, "_destroy" => truth },
|
66
|
+
"foo" => { "id" => phone_two.id, "number" => "3" },
|
67
|
+
"baz" => { "number" => "4" }
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
it "removes the first document from the relation" do
|
72
|
+
expect(persisted.paranoid_phones.size).to eq(2)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not delete the unmarked document" do
|
76
|
+
expect(persisted.paranoid_phones.first.number).to eq("3")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "adds the new document to the relation" do
|
80
|
+
expect(persisted.paranoid_phones.last.number).to eq("4")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "has the proper persisted count" do
|
84
|
+
expect(persisted.paranoid_phones.count).to eq(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "soft deletes the removed document" do
|
88
|
+
expect(phone_one).to be_destroyed
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when saving the parent" do
|
92
|
+
|
93
|
+
before do
|
94
|
+
persisted.with(safe: true).save
|
95
|
+
end
|
96
|
+
|
97
|
+
it "deletes the marked document from the relation" do
|
98
|
+
expect(persisted.reload.paranoid_phones.count).to eq(2)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "does not delete the unmarked document" do
|
102
|
+
expect(persisted.reload.paranoid_phones.first.number).to eq("3")
|
103
|
+
end
|
104
|
+
|
105
|
+
it "persists the new document to the relation" do
|
106
|
+
expect(persisted.reload.paranoid_phones.last.number).to eq("4")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when the child has defaults" do
|
116
|
+
|
117
|
+
before(:all) do
|
118
|
+
Person.accepts_nested_attributes_for :appointments, allow_destroy: true
|
119
|
+
end
|
120
|
+
|
121
|
+
after(:all) do
|
122
|
+
Person.send(:undef_method, :appointments_attributes=)
|
123
|
+
end
|
124
|
+
context "when the parent is persisted" do
|
125
|
+
context "when the child returns false in a before callback" do
|
126
|
+
context "when the child is paranoid" do
|
127
|
+
|
128
|
+
before(:all) do
|
129
|
+
Person.accepts_nested_attributes_for :paranoid_phones, allow_destroy: true
|
130
|
+
end
|
131
|
+
|
132
|
+
after(:all) do
|
133
|
+
Person.send(:undef_method, :paranoid_phones=)
|
134
|
+
Person.accepts_nested_attributes_for :paranoid_phones
|
135
|
+
end
|
136
|
+
|
137
|
+
let!(:persisted) do
|
138
|
+
Person.create(age: 42)
|
139
|
+
end
|
140
|
+
|
141
|
+
let!(:phone) do
|
142
|
+
persisted.paranoid_phones.create
|
143
|
+
end
|
144
|
+
|
145
|
+
before do
|
146
|
+
persisted.paranoid_phones_attributes =
|
147
|
+
{ "foo" => { "id" => phone.id, "number" => 42, "_destroy" => true }}
|
148
|
+
end
|
149
|
+
|
150
|
+
it "does not destroy the child" do
|
151
|
+
expect(persisted.reload.paranoid_phones).not_to be_empty
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|