immortal 1.0.4 → 1.0.5

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/README.md CHANGED
@@ -41,6 +41,7 @@ If you want to improve immortal
41
41
 
42
42
  ## CHANGELOG
43
43
 
44
+ - 1.0.5 Use separate internal accessors for with/only_deleted singular association readers
44
45
  - 1.0.4 Extract with_deleted singular assoc readers to separate module
45
46
  - 1.0.3 Added back feature where using immortal finders doesn't unscope association scopes.
46
47
  - 1.0.2 Added with/only_deleted singular association readers (see specs)
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "immortal"
6
- s.version = '1.0.4'
6
+ s.version = '1.0.5'
7
7
  s.authors = ["Jordi Romero", "Saimon Moore"]
8
8
  s.email = ["jordi@jrom.net", "saimon@saimonmoore.net"]
9
9
  s.homepage = "http://github.com/teambox/immortal"
@@ -1,33 +1,147 @@
1
1
  module Immortal
2
2
  module SingularAssociation
3
+ attr_reader :with_deleted_target, :only_deleted_target
3
4
 
4
5
  def with_deleted_reader(force_reload = false)
5
- deleted_reader('with', force_reload)
6
+ reader_with_deleted(force_reload)
6
7
  end
7
8
 
8
9
  def only_deleted_reader(force_reload = false)
9
- deleted_reader('only', force_reload)
10
+ reader_only_deleted(force_reload)
10
11
  end
11
12
 
12
13
  private
13
14
 
14
- def deleted_reader(how, force_reload = false)
15
- klass.uncached do
16
- send(:"find_#{how}_deleted_target")
15
+ def reset_with_deleted
16
+ @with_deleted_loaded = false
17
+ ActiveRecord::IdentityMap.remove(with_deleted_target) if ActiveRecord::IdentityMap.enabled? && with_deleted_target
18
+ @with_deleted_target = nil
19
+ end
20
+
21
+ def reset_only_deleted
22
+ @only_deleted_loaded = false
23
+ ActiveRecord::IdentityMap.remove(only_deleted_target) if ActiveRecord::IdentityMap.enabled? && only_deleted_target
24
+ @only_deleted_target = nil
25
+ end
26
+
27
+ def with_deleted_loaded!
28
+ @with_deleted_loaded = true
29
+ @with_deleted_stale_state = stale_state
30
+ end
31
+
32
+ def only_deleted_loaded!
33
+ @only_deleted_loaded = true
34
+ @only_deleted_stale_state = stale_state
35
+ end
36
+
37
+ def with_deleted_loaded?
38
+ @with_deleted_loaded
39
+ end
40
+
41
+ def only_deleted_loaded?
42
+ @only_deleted_loaded
43
+ end
44
+
45
+ def stale_with_deleted_target?
46
+ with_deleted_loaded? && @with_deleted_stale_state != stale_state
47
+ end
48
+
49
+ def stale_only_deleted_target?
50
+ only_deleted_loaded? && @only_deleted_stale_state != stale_state
51
+ end
52
+
53
+ def reload_only_deleted
54
+ reset_only_deleted
55
+ reset_scope
56
+ load_only_deleted_target
57
+ self unless only_deleted_target.nil?
58
+ end
59
+
60
+ def reload_with_deleted
61
+ reset_with_deleted
62
+ reset_scope
63
+ load_with_deleted_target
64
+ self unless with_deleted_target.nil?
65
+ end
66
+
67
+ def find_with_deleted_target?
68
+ !with_deleted_loaded? && (!owner.new_record? || foreign_key_present?) && klass
69
+ end
70
+
71
+ def find_only_deleted_target?
72
+ !only_deleted_loaded? && (!owner.new_record? || foreign_key_present?) && klass
73
+ end
74
+
75
+ def load_with_deleted_target
76
+ if find_with_deleted_target?
77
+ begin
78
+ if ActiveRecord::IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
79
+ @with_deleted_target = ActiveRecord::IdentityMap.get(association_class, owner[reflection.foreign_key])
80
+ end
81
+ rescue NameError
82
+ nil
83
+ ensure
84
+ @with_deleted_target ||= find_with_deleted_target
17
85
  end
18
86
  end
87
+ with_deleted_loaded! unless with_deleted_loaded?
88
+ with_deleted_target
89
+ rescue ActiveRecord::RecordNotFound
90
+ with_deleted_reset
91
+ end
19
92
 
20
- def find_with_deleted_target
21
- klass.unscoped do
22
- scoped.first.tap { |record| set_inverse_instance(record) }
93
+ def load_only_deleted_target
94
+ if find_only_deleted_target?
95
+ begin
96
+ if ActiveRecord::IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
97
+ @only_deleted_target = ActiveRecord::IdentityMap.get(association_class, owner[reflection.foreign_key])
98
+ end
99
+ rescue NameError
100
+ nil
101
+ ensure
102
+ @only_deleted_target ||= find_only_deleted_target
23
103
  end
24
104
  end
105
+ only_deleted_loaded! unless only_deleted_loaded?
106
+ only_deleted_target
107
+ rescue ActiveRecord::RecordNotFound
108
+ only_deleted_reset
109
+ end
25
110
 
26
- def find_only_deleted_target
27
- klass.unscoped do
28
- scoped.where(:deleted => true).first.tap { |record| set_inverse_instance(record) }
29
- end
111
+ def reader_with_deleted(force_reload = false)
112
+ if force_reload
113
+ klass.uncached { reload_with_deleted } if klass
114
+ elsif !with_deleted_loaded? || stale_with_deleted_target?
115
+ reload_with_deleted
116
+ end
117
+
118
+ with_deleted_target
119
+ end
120
+
121
+ def reader_only_deleted(force_reload = false)
122
+
123
+ if force_reload
124
+ klass.uncached { reload_only_deleted } if klass
125
+ elsif !only_deleted_loaded? || stale_only_deleted_target?
126
+ reload_only_deleted
127
+ end
128
+
129
+ only_deleted_target
130
+ end
131
+
132
+ def find_with_deleted_target
133
+ return nil unless klass
134
+ klass.unscoped do
135
+ scoped.first.tap { |record| set_inverse_instance(record) }
136
+ end
137
+ end
138
+
139
+ def find_only_deleted_target
140
+ return nil unless klass
141
+ klass.unscoped do
142
+ scoped.where(:deleted => true).first.tap { |record| set_inverse_instance(record) }
30
143
  end
144
+ end
31
145
 
32
146
  end
33
147
  end
@@ -234,12 +234,13 @@ describe Immortal do
234
234
  #don't assign directly and destroy new target
235
235
  target_2.destroy
236
236
 
237
- #Respect what's expected
238
- node.target.should be_nil
239
-
240
237
  #Ask for deleted target (or not deleted). Will NOT cache
238
+ # Run this before default accessor to test scope has been reset.
241
239
  node.target_with_deleted.should == target_2
242
240
 
241
+ #Respect what's expected
242
+ node.target.should be_nil
243
+
243
244
  #Ask only for deleted target. Will NOT cache
244
245
  node.target_only_deleted.should == target_2
245
246
 
@@ -260,7 +261,7 @@ describe Immortal do
260
261
  node.target = target_1
261
262
  node.target.should == target_1
262
263
 
263
- #switch target indirectly
264
+ #switch target directly
264
265
  node.target = target_2
265
266
 
266
267
  node.target.should == target_2
@@ -279,6 +280,49 @@ describe Immortal do
279
280
  node.target.should be_nil
280
281
  end
281
282
 
283
+ it "deleted readers should respect staleness" do
284
+ #setup
285
+ node = ImmortalNode.create! :title => 'testing association 1'
286
+ target_1 = ImmortalSomeTarget.create! :title => 'target 1'
287
+ target_2 = ImmortalSomeOtherTarget.create! :title => 'target 2'
288
+
289
+ #confirm initial state
290
+ node.target.should be_nil
291
+ node.target_with_deleted.should be_nil
292
+ node.target_only_deleted.should be_nil
293
+
294
+ #load target & confirm
295
+ node.target = target_1
296
+ node.target.should == target_1
297
+ node.target_with_deleted.should == target_1
298
+ node.target_only_deleted.should be_nil
299
+
300
+ #switch target directly
301
+ node.target = target_2
302
+
303
+ node.target.should == target_2
304
+ node.target_with_deleted.should == target_2
305
+
306
+ #don't assign directly and destroy new target
307
+ target_2.destroy
308
+
309
+ #Respect what's expected
310
+ node.target(true).should be_nil
311
+
312
+ #Ask for deleted target (or not deleted).
313
+ node.target_with_deleted.should == target_2
314
+ node.target_only_deleted.should == target_2
315
+
316
+ #Confirm we haven't invaded the target namespace
317
+ node.target.should be_nil
318
+
319
+ node.target_id = target_1.id
320
+ node.target_type = target_1.class.name
321
+ node.target.should == target_1
322
+ node.target_with_deleted.should == target_1
323
+ node.target_only_deleted.should be_nil
324
+ end
325
+
282
326
  it "should not unscope associations when using with_deleted scope" do
283
327
  m1 = ImmortalModel.create! :title => 'previously created model'
284
328
  n1 = ImmortalNode.create! :title => 'previously created association'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immortal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-10-17 00:00:00.000000000 Z
13
+ date: 2011-10-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
17
- requirement: &70364462440520 !ruby/object:Gem::Requirement
17
+ requirement: &70196367376140 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 3.1.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70364462440520
25
+ version_requirements: *70196367376140
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &70364462440040 !ruby/object:Gem::Requirement
28
+ requirement: &70196367375660 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 2.6.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70364462440040
36
+ version_requirements: *70196367375660
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: sqlite3
39
- requirement: &70364462439660 !ruby/object:Gem::Requirement
39
+ requirement: &70196367375280 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70364462439660
47
+ version_requirements: *70196367375280
48
48
  description: Typical paranoid gem built for Rails 3 and with the minimum code needed
49
49
  to satisfy acts_as_paranoid's API
50
50
  email: