sepastian-mongoid-paranoia-rails4 0.0.1.alpha
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 +17 -0
- data/.rspec +3 -0
- data/.travis.yml +20 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +68 -0
- data/Rakefile +13 -0
- data/lib/mongoid-paranoia.rb +1 -0
- data/lib/mongoid/paranoia.rb +137 -0
- data/lib/mongoid/paranoia/monkey_patches.rb +95 -0
- data/lib/mongoid/validatable/uniqueness_including_deleted_validator.rb +38 -0
- data/mongoid-paranoia-rails4.gemspec +23 -0
- data/spec/app/models/address.rb +69 -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 +33 -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/nested_attributes_spec.rb +164 -0
- data/spec/mongoid/paranoia_spec.rb +730 -0
- data/spec/mongoid/scoping_spec.rb +55 -0
- data/spec/mongoid/validatable/uniqueness_spec.rb +58 -0
- data/spec/spec_helper.rb +81 -0
- metadata +140 -0
@@ -0,0 +1,69 @@
|
|
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, where(street: "Rodeo Dr") do
|
40
|
+
def mansion?
|
41
|
+
all? { |address| address.street == "Rodeo Dr" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
validates_presence_of :street, on: :update
|
46
|
+
validates_format_of :street, with: /\D/, allow_nil: true
|
47
|
+
|
48
|
+
def set_parent=(set = false)
|
49
|
+
self.parent_title = addressable.title if set
|
50
|
+
end
|
51
|
+
|
52
|
+
def <=>(other)
|
53
|
+
street <=> other.street
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def california
|
58
|
+
where(state: "CA")
|
59
|
+
end
|
60
|
+
|
61
|
+
def homes
|
62
|
+
where(address_type: "Home")
|
63
|
+
end
|
64
|
+
|
65
|
+
def streets
|
66
|
+
all.map(&:street)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
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,33 @@
|
|
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
|
+
|
9
|
+
belongs_to :person
|
10
|
+
|
11
|
+
has_and_belongs_to_many :tags
|
12
|
+
has_many :authors, dependent: :delete, inverse_of: :post
|
13
|
+
has_many :titles, dependent: :restrict
|
14
|
+
|
15
|
+
scope :recent, where(created_at: { "$lt" => Time.now, "$gt" => 30.days.ago })
|
16
|
+
|
17
|
+
before_destroy :before_destroy_stub
|
18
|
+
after_destroy :after_destroy_stub
|
19
|
+
|
20
|
+
def before_destroy_stub
|
21
|
+
self.before_destroy_called = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_destroy_stub
|
25
|
+
self.after_destroy_called = true
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def old
|
30
|
+
where(created_at: { "$lt" => 30.days.ago })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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, default: ->{ 1.day.ago.in_time_zone("Alaska") }
|
21
|
+
field :ssn
|
22
|
+
field :owner_id, type: Integer
|
23
|
+
field :security_code
|
24
|
+
field :reading, type: Object
|
25
|
+
field :bson_id, type: Moped::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,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
|
+
persisted.paranoid_phones.size.should eq(2)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not delete the unmarked document" do
|
76
|
+
persisted.paranoid_phones.first.number.should eq("3")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "adds the new document to the relation" do
|
80
|
+
persisted.paranoid_phones.last.number.should eq("4")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "has the proper persisted count" do
|
84
|
+
persisted.paranoid_phones.count.should eq(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "soft deletes the removed document" do
|
88
|
+
phone_one.should 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
|
+
persisted.reload.paranoid_phones.count.should eq(2)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "does not delete the unmarked document" do
|
102
|
+
persisted.reload.paranoid_phones.first.number.should eq("3")
|
103
|
+
end
|
104
|
+
|
105
|
+
it "persists the new document to the relation" do
|
106
|
+
persisted.reload.paranoid_phones.last.number.should 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
|
+
persisted.reload.paranoid_phones.should_not 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
|