semanticart-is_paranoid 0.9.5 → 0.9.6

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/CHANGELOG CHANGED
@@ -1,5 +1,8 @@
1
1
  This will only document major changes. Please see the commit log for minor changes.
2
2
 
3
+ -2009-09-25
4
+ * fixing bug with has_many :through not respecting is_paranoid conditions (thanks, Ben Johnson)
5
+
3
6
  -2009-09-18
4
7
  * making is_paranoid play nice with habtm relationships
5
8
 
@@ -1,5 +1,15 @@
1
1
  h1. is_paranoid ( same as it ever was )
2
2
 
3
+ h3. advice and disclaimer
4
+
5
+ You should always declare is_paranoid before any associations in your model unless you have a good reason for doing otherwise. Some relationships might not behave properly if you fail to do so. If you know what you're doing (and have written tests) and want to supress the warning then you can pass :suppress_load_order_warning => true as an option.
6
+
7
+ <pre>
8
+ is_paranoid :suppress_load_order_warning => true
9
+ </pre>
10
+
11
+ You should never expect _any_ library to work or behave exactly how you want it to: test, test, test and file an issue if you have any problems. Bonus points if you include sample failing code. Extra bonus points if you send a pull request that implements a feature/fixes a bug.
12
+
3
13
  h3. and you may ask yourself, well, how did I get here?
4
14
 
5
15
  Sometimes you want to delete something in ActiveRecord, but you realize you might need it later (for an undo feature, or just as a safety net, etc.). There are a plethora of plugins that accomplish this, the most famous of which is the venerable acts_as_paranoid which is great but not really actively developed any more. What's more, acts_as_paranoid was written for an older version of ActiveRecord and, with default_scope in 2.3, it is now possible to do the same thing with significantly less complexity. Thus, *is_paranoid*.
@@ -91,7 +101,7 @@ And now the validates_uniqueness_of will ignore items that are destroyed.
91
101
 
92
102
  h3. and you may ask yourself, where does that highway go to?
93
103
 
94
- If you find any bugs, have any ideas of features you think are missing, or find things you're like to see work differently, feel free to send me a message or a pull request.
104
+ If you find any bugs, have any ideas of features you think are missing, or find things you're like to see work differently, feel free to file an issue or send a pull request.
95
105
 
96
106
  Currently on the todo list:
97
107
  * add options for merging additional default_scope options (i.e. order, etc.)
@@ -100,4 +110,4 @@ h3. Thanks
100
110
 
101
111
  Thanks to Rick Olson for acts_as_paranoid which is obviously an inspiration in concept and execution, Ryan Bates for mentioning the idea of using default_scope for this on Ryan Daigle's "post introducing default_scope":defscope, and the Talking Heads for being the Talking Heads.
102
112
 
103
- [defscope]http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping
113
+ [defscope]http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 9
3
- :patch: 5
2
+ :patch: 6
4
3
  :major: 0
4
+ :minor: 9
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{is_paranoid}
8
- s.version = "0.9.5"
8
+ s.version = "0.9.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jeffrey Chupp"]
12
- s.date = %q{2009-09-18}
12
+ s.date = %q{2009-09-26}
13
13
  s.description = %q{}
14
14
  s.email = %q{jeff@semanticart.com}
15
15
  s.extra_rdoc_files = [
@@ -15,6 +15,10 @@ module IsParanoid
15
15
  class_inheritable_accessor :destroyed_field, :field_destroyed, :field_not_destroyed
16
16
  self.destroyed_field, self.field_destroyed, self.field_not_destroyed = opts[:field]
17
17
 
18
+ if self.reflect_on_all_associations.size > 0 && ! opts[:suppress_load_order_warning]
19
+ warn "is_paranoid warning in class #{self}: You should declare is_paranoid before your associations"
20
+ end
21
+
18
22
  # This is the real magic. All calls made to this model will append
19
23
  # the conditions deleted_at => nil (or whatever your destroyed_field
20
24
  # and field_not_destroyed are). All exceptions require using
@@ -27,6 +31,25 @@ module IsParanoid
27
31
  end
28
32
 
29
33
  module ClassMethods
34
+ def is_or_equals_not_destroyed
35
+ if [nil, 'NULL'].include?(field_not_destroyed)
36
+ 'IS NULL'
37
+ else
38
+ "= #{field_not_destroyed}"
39
+ end
40
+ end
41
+
42
+ # ensure that we respect the is_paranoid conditions when being loaded as a has_many :through
43
+ # NOTE: this only works if is_paranoid is declared before has_many relationships.
44
+ def has_many(association_id, options = {}, &extension)
45
+ if options.key?(:through)
46
+ conditions = "#{options[:through].to_s.pluralize}.#{destroyed_field} #{is_or_equals_not_destroyed}"
47
+ options[:conditions] = "(" + [options[:conditions], conditions].compact.join(") AND (") + ")"
48
+ end
49
+ super
50
+ end
51
+
52
+
30
53
  # Actually delete the model, bypassing the safety net. Because
31
54
  # this method is called internally by Model.delete(id) and on the
32
55
  # delete method in each instance, we don't need to specify those
@@ -72,6 +72,24 @@ describe IsParanoid do
72
72
  Android.count_with_destroyed.should == 2
73
73
  end
74
74
 
75
+ it "shouldn't have problems with has_many :through relationships" do
76
+ # TODO: this spec can be cleaner and more specific, replace it later
77
+ # Dings use a boolean non-standard is_paranoid field
78
+ # Scratch uses the defaults. Testing both ensures compatibility
79
+ [[:dings, Ding], [:scratches, Scratch]].each do |method, klass|
80
+ @r2d2.dings.should == []
81
+
82
+ dent = Dent.create(:description => 'really terrible', :android_id => @r2d2.id)
83
+ item = klass.create(:description => 'quite nasty', :dent_id => dent.id)
84
+ @r2d2.reload
85
+ @r2d2.send(method).should == [item]
86
+
87
+ dent.destroy
88
+ @r2d2.reload
89
+ @r2d2.send(method).should == []
90
+ end
91
+ end
92
+
75
93
  it "should not choke has_and_belongs_to_many relationships" do
76
94
  @r2d2.places.should include(@tatooine)
77
95
  @tatooine.destroy
@@ -298,5 +316,4 @@ describe IsParanoid do
298
316
  uuid.destroy.should be_true
299
317
  end
300
318
  end
301
-
302
319
  end
@@ -4,14 +4,16 @@ class Person < ActiveRecord::Base #:nodoc:
4
4
  end
5
5
 
6
6
  class Android < ActiveRecord::Base #:nodoc:
7
+ is_paranoid
7
8
  validates_uniqueness_of :name
8
9
  has_many :components, :dependent => :destroy
9
10
  has_one :sticker
10
11
  has_many :memories, :foreign_key => 'parent_id'
12
+ has_many :dents
13
+ has_many :dings, :through => :dents
14
+ has_many :scratches, :through => :dents
11
15
  has_and_belongs_to_many :places
12
16
 
13
- is_paranoid
14
-
15
17
  # this code is to ensure that our destroy and restore methods
16
18
  # work without triggering before/after_update callbacks
17
19
  before_update :raise_hell
@@ -20,6 +22,23 @@ class Android < ActiveRecord::Base #:nodoc:
20
22
  end
21
23
  end
22
24
 
25
+ class Dent < ActiveRecord::Base #:nodoc:
26
+ is_paranoid
27
+ belongs_to :android
28
+ has_many :dings
29
+ has_many :scratches
30
+ end
31
+
32
+ class Ding < ActiveRecord::Base #:nodoc:
33
+ is_paranoid :field => [:not_deleted, true, false]
34
+ belongs_to :dent
35
+ end
36
+
37
+ class Scratch < ActiveRecord::Base #:nodoc:
38
+ is_paranoid
39
+ belongs_to :dent
40
+ end
41
+
23
42
  class Component < ActiveRecord::Base #:nodoc:
24
43
  is_paranoid
25
44
  belongs_to :android, :dependent => :destroy
@@ -7,6 +7,24 @@ ActiveRecord::Schema.define(:version => 20090317164830) do
7
7
  t.datetime "updated_at"
8
8
  end
9
9
 
10
+ create_table "dents", :force => true do |t|
11
+ t.integer "android_id"
12
+ t.string "description"
13
+ t.datetime "deleted_at"
14
+ end
15
+
16
+ create_table "dings", :force => true do |t|
17
+ t.integer "dent_id"
18
+ t.string "description"
19
+ t.boolean "not_deleted"
20
+ end
21
+
22
+ create_table "scratches", :force => true do |t|
23
+ t.integer "dent_id"
24
+ t.string "description"
25
+ t.datetime "deleted_at"
26
+ end
27
+
10
28
  create_table "androids_places", :force => true, :id => false do |t|
11
29
  t.integer "android_id"
12
30
  t.integer "place_id"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semanticart-is_paranoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey Chupp
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-18 00:00:00 -07:00
12
+ date: 2009-09-26 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15