immortal 0.1.4 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- immortal (0.1.3)
5
- activerecord (~> 3.0.3)
4
+ immortal (0.1.7)
5
+ activerecord (~> 3.0.5)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- activemodel (3.0.3)
11
- activesupport (= 3.0.3)
10
+ activemodel (3.0.9)
11
+ activesupport (= 3.0.9)
12
12
  builder (~> 2.1.2)
13
- i18n (~> 0.4)
14
- activerecord (3.0.3)
15
- activemodel (= 3.0.3)
16
- activesupport (= 3.0.3)
17
- arel (~> 2.0.2)
13
+ i18n (~> 0.5.0)
14
+ activerecord (3.0.9)
15
+ activemodel (= 3.0.9)
16
+ activesupport (= 3.0.9)
17
+ arel (~> 2.0.10)
18
18
  tzinfo (~> 0.3.23)
19
- activesupport (3.0.3)
20
- arel (2.0.7)
19
+ activesupport (3.0.9)
20
+ arel (2.0.10)
21
21
  builder (2.1.2)
22
22
  columnize (0.3.2)
23
23
  diff-lcs (1.1.2)
@@ -37,7 +37,7 @@ GEM
37
37
  ruby-debug-base (0.10.4)
38
38
  linecache (>= 0.3)
39
39
  sqlite3-ruby (1.3.2)
40
- tzinfo (0.3.24)
40
+ tzinfo (0.3.29)
41
41
 
42
42
  PLATFORMS
43
43
  ruby
data/README.md CHANGED
@@ -44,6 +44,9 @@ If you want to improve immortal
44
44
 
45
45
  ## CHANGELOG
46
46
 
47
+ - 0.1.6 Fixing immortal issue 2: with_deleted breaks associations
48
+ - 0.1.5 Add "without deleted" scope to join model by overriding HasManyThroughAssociation#construct_conditions
49
+ rather than simply adding to has_many conditions.
47
50
  - 0.1.4 fix bug where ALL records of any dependent associations were
48
51
  immortally deleted if assocation has :dependant => :delete_all option
49
52
  set
@@ -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 = '0.1.4'
6
+ s.version = '0.1.7'
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Jordi Romero", "Saimon Moore"]
9
9
  s.email = ["jordi@jrom.net", "saimon@saimonmoore.net"]
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
 
19
- s.add_dependency 'activerecord', '~> 3.0.3'
19
+ s.add_dependency 'activerecord', '~> 3.0.5'
20
20
  s.add_development_dependency 'rspec', '~> 2.3.0'
21
21
  s.add_development_dependency 'sqlite3-ruby', '~> 1.3.2'
22
22
  s.add_development_dependency 'ruby-debug', '~> 0.10.4'
@@ -1,9 +1,28 @@
1
+ require 'immortal/has_many_through_mortal_association'
2
+
1
3
  module Immortal
4
+
2
5
  def self.included(base)
3
6
  base.send :extend, ClassMethods
4
7
  base.send :include, InstanceMethods
5
8
  base.class_eval do
6
9
  class << self
10
+ # In has_many :through => join_model we have to explicitly add
11
+ # the 'not deleted' scope, otherwise it will take all the rows
12
+ # from the join model
13
+ def has_many_mortal(association_id, options = {}, &extension)
14
+ has_many_immortal(association_id, options, &extension).tap do
15
+ if options[:through] and reflections[options[:through]] and reflections[options[:through]].class_name.classify.constantize.arel_table[:deleted]
16
+ reflection = reflect_on_association(association_id)
17
+ collection_reader_method(reflection, Immortal::HasManyThroughMortalAssociation)
18
+ collection_accessor_methods(reflection, Immortal::HasManyThroughMortalAssociation, false)
19
+ end
20
+ end
21
+ end
22
+
23
+ alias_method :has_many_immortal, :has_many
24
+ alias_method :has_many, :has_many_mortal
25
+
7
26
  alias :mortal_delete_all :delete_all
8
27
  alias :delete_all :immortal_delete_all
9
28
  end
@@ -12,12 +31,20 @@ module Immortal
12
31
 
13
32
  module ClassMethods
14
33
 
34
+ def immortal?
35
+ self.included_modules.include?(::Immortal::InstanceMethods)
36
+ end
37
+
15
38
  def with_deleted
16
- unscoped
39
+ except(:where).where(filter_undeleted_where_clauses.join(' AND '))
17
40
  end
18
41
 
19
42
  def only_deleted
20
- unscoped.where(:deleted => true)
43
+ deleted_clause = arel_table[:deleted].eq(true)
44
+ where_sql_clauses = filter_undeleted_where_clauses
45
+ where_sql_clauses.concat unscoped.where(deleted_clause).where_clauses
46
+
47
+ except(:where).where(where_sql_clauses.join(" AND "))
21
48
  end
22
49
 
23
50
  def count_with_deleted(*args)
@@ -44,15 +71,25 @@ module Immortal
44
71
  unscoped.mortal_delete_all(*args)
45
72
  end
46
73
 
47
- # In has_many :through => join_model we have to explicitly add
48
- # the 'not deleted' scope, otherwise it will take all the rows
49
- # from the join model
50
- def has_many(association_id, options = {}, &extension)
51
- if options.key?(:through) and options[:through].to_s.classify.constantize.arel_table[:deleted]
52
- conditions = "#{options[:through].to_s.pluralize}.deleted IS NULL OR #{options[:through].to_s.pluralize}.deleted = ?"
53
- options[:conditions] = ["(" + [options[:conditions], conditions].compact.join(") AND (") + ")", false]
54
- end
55
- super
74
+ def undeleted_clause_sql
75
+ unscoped.where(arel_table[:deleted].eq(nil).or(arel_table[:deleted].eq(false))).where_clauses.first
76
+ end
77
+
78
+ def deleted_clause_sql
79
+ unscoped.where(arel_table[:deleted].eq(true)).where_clauses.first
80
+ end
81
+
82
+ private
83
+
84
+ def filter_undeleted_where_clauses
85
+ where_clauses = scoped.arel.send(:where_clauses)
86
+
87
+ #Yes it's an ugly hack but I couldn't find a cleaner way of doing this
88
+ filtered_clauses = where_clauses.dup.map do |clause|
89
+ clause.gsub(/(\s+AND\s+)?\("?`?(\w+)`?"?."?`?deleted`?"?\sIS\sNULL[^\)]+\)/, '')
90
+ end.map {|cl| cl.gsub("()", '')}.select {|cl| !cl.empty?}
91
+
92
+ filtered_clauses
56
93
  end
57
94
 
58
95
  end
@@ -0,0 +1,23 @@
1
+ require 'active_record'
2
+
3
+ module Immortal
4
+
5
+ class HasManyThroughMortalAssociation < ActiveRecord::Associations::HasManyThroughAssociation
6
+ protected
7
+
8
+ def construct_conditions
9
+ klass = @reflection.through_reflection.klass
10
+ return super unless klass.respond_to?(:immortal?) && klass.immortal?
11
+ table_name = @reflection.through_reflection.quoted_table_name
12
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
13
+ "#{table_name}.#{attr} = #{value}"
14
+ end
15
+
16
+ deleted_conditions = ["#{table_name}.deleted IS NULL OR #{table_name}.deleted = ?", false]
17
+ conditions << klass.send(:sanitize_sql, deleted_conditions)
18
+ conditions << sql_conditions if sql_conditions
19
+ "(" + conditions.join(') AND (') + ")"
20
+ end
21
+ end
22
+
23
+ end
@@ -181,13 +181,13 @@ describe Immortal do
181
181
  @n = ImmortalNode.create! :title => 'testing association'
182
182
  @join = ImmortalJoin.create! :immortal_model => @m, :immortal_node => @n
183
183
 
184
- @m.immortal_nodes.count.should == 1
185
- @n.immortal_models.count.should == 1
184
+ @m.nodes.count.should == 1
185
+ @n.models.count.should == 1
186
186
 
187
187
  @join.destroy
188
188
 
189
- @m.immortal_nodes.count.should == 0
190
- @n.immortal_models.count.should == 0
189
+ @m.nodes.count.should == 0
190
+ @n.models.count.should == 0
191
191
  end
192
192
 
193
193
  it "should only immortally delete scoped associations, NOT ALL RECORDS" do
@@ -205,4 +205,43 @@ describe Immortal do
205
205
  [n1,n2,j1,j2].all? {|r| r.reload.deleted?}.should be_true
206
206
  [n3,j3].all? {|r| !r.reload.deleted?}.should be_true
207
207
  end
208
+
209
+ it "should properly generate joins" do
210
+ join_sql = 'INNER JOIN "immortal_joins" ON "immortal_nodes"."id" = "immortal_joins"."immortal_node_id" INNER JOIN "immortal_models" ON "immortal_models"."id" = "immortal_joins"."immortal_model_id"'
211
+ ImmortalNode.joins(:immortal_models).to_sql.should include(join_sql)
212
+ end
213
+
214
+ it "should not unscope associations when using with_deleted scope" do
215
+ m1 = ImmortalModel.create! :title => 'previously created model'
216
+ n1 = ImmortalNode.create! :title => 'previously created association'
217
+ j1 = ImmortalJoin.create! :immortal_model => m1, :immortal_node => n1
218
+
219
+ @n = ImmortalNode.create! :title => 'testing association'
220
+ @join = ImmortalJoin.create! :immortal_model => @m, :immortal_node => @n
221
+
222
+ @join.destroy
223
+
224
+ @m.nodes.count.should == 0
225
+ @n.joins.count.should == 0
226
+
227
+ @m.nodes.with_deleted.count.should == 1
228
+ @n.joins.with_deleted.count.should == 1
229
+ end
230
+
231
+ it "should not unscope associations when using only_deleted scope" do
232
+ m1 = ImmortalModel.create! :title => 'previously created model'
233
+ n1 = ImmortalNode.create! :title => 'previously created association'
234
+ j1 = ImmortalJoin.create! :immortal_model => m1, :immortal_node => n1
235
+
236
+ @n = ImmortalNode.create! :title => 'testing association'
237
+ @join = ImmortalJoin.create! :immortal_model => @m, :immortal_node => @n
238
+
239
+ @join.destroy
240
+
241
+ @m.nodes.count.should == 0
242
+ @n.joins.count.should == 0
243
+
244
+ @m.nodes.only_deleted.count.should == 1
245
+ @n.joins.only_deleted.count.should == 1
246
+ end
208
247
  end
@@ -50,7 +50,7 @@ class ImmortalJoin < ActiveRecord::Base
50
50
  include Immortal
51
51
 
52
52
  belongs_to :immortal_model
53
- belongs_to :immortal_node
53
+ belongs_to :immortal_node, :dependent => :destroy
54
54
  end
55
55
 
56
56
  class ImmortalNode < ActiveRecord::Base
@@ -58,6 +58,9 @@ class ImmortalNode < ActiveRecord::Base
58
58
 
59
59
  has_many :immortal_joins
60
60
  has_many :immortal_models, :through => :immortal_joins
61
+
62
+ has_many :joins, :class_name => 'ImmortalJoin'
63
+ has_many :models, :through => :joins, :source => :immortal_model
61
64
  end
62
65
 
63
66
  class ImmortalModel < ActiveRecord::Base
@@ -66,6 +69,9 @@ class ImmortalModel < ActiveRecord::Base
66
69
  has_many :immortal_nodes, :through => :immortal_joins, :dependent => :destroy
67
70
  has_many :immortal_joins, :dependent => :delete_all
68
71
 
72
+ has_many :joins, :class_name => 'ImmortalJoin', :dependent => :delete_all
73
+ has_many :nodes, :through => :joins, :source => :immortal_node, :dependent => :destroy
74
+
69
75
  attr_accessor :before_d, :after_d, :before_u, :after_u
70
76
 
71
77
  before_destroy :set_before
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immortal
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 4
10
- version: 0.1.4
9
+ - 7
10
+ version: 0.1.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jordi Romero
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-01-19 00:00:00 +01:00
19
+ date: 2011-08-30 00:00:00 +02:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -27,12 +27,12 @@ dependencies:
27
27
  requirements:
28
28
  - - ~>
29
29
  - !ruby/object:Gem::Version
30
- hash: 1
30
+ hash: 13
31
31
  segments:
32
32
  - 3
33
33
  - 0
34
- - 3
35
- version: 3.0.3
34
+ - 5
35
+ version: 3.0.5
36
36
  type: :runtime
37
37
  version_requirements: *id001
38
38
  - !ruby/object:Gem::Dependency
@@ -101,6 +101,7 @@ files:
101
101
  - Rakefile
102
102
  - immortal.gemspec
103
103
  - lib/immortal.rb
104
+ - lib/immortal/has_many_through_mortal_association.rb
104
105
  - spec/immortal_spec.rb
105
106
  - spec/spec_helper.rb
106
107
  has_rdoc: true