immortal 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
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: