immortal 0.1.4 → 0.1.7

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.
@@ -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