deferred_associations 0.5.7 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -0
- data/Readme.markdown +3 -3
- data/VERSION +1 -1
- data/deferred_associations.gemspec +41 -44
- data/lib/array_to_association_wrapper.rb +68 -67
- data/lib/has_and_belongs_to_many_with_deferred_save.rb +34 -53
- data/lib/has_many_with_deferred_save.rb +107 -113
- data/spec/db/schema.rb +20 -22
- data/spec/habtm_ar4_spec.rb +85 -0
- data/spec/has_and_belongs_to_many_with_deferred_save_spec.rb +125 -128
- data/spec/has_many_with_deferred_save_spec.rb +103 -104
- data/spec/models/chair.rb +5 -3
- data/spec/models/door.rb +2 -0
- data/spec/models/person.rb +5 -3
- data/spec/models/room.rb +8 -9
- data/spec/models/table.rb +6 -4
- data/spec/spec_helper.rb +14 -12
- metadata +6 -5
- data/Rakefile +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 868bf45a163b6381d3c286bf65251e0b3e6235f6
|
4
|
+
data.tar.gz: 1e12fb0d5b42f22ce07a12f01fe3fab812508779
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99860be2db82c21a509355252729c0f4b716d1b99e46d2c2203feba4053dc1cc93a758a46d0725fd639c72aaddbe76104f3825e275a5e815bf2e98bdfb46149f
|
7
|
+
data.tar.gz: 216aebba563c6374607036215dec3ee3bc55f1a7d9ccf3863f9cc9aa4c8327571696998567594d05b219df6455ed9888d186e94e334f7b4e18f0a9284fa6926e
|
data/CHANGELOG
CHANGED
data/Readme.markdown
CHANGED
@@ -39,7 +39,7 @@ Usage
|
|
39
39
|
Compatibility
|
40
40
|
=============
|
41
41
|
|
42
|
-
Tested with Rails 2.3.
|
42
|
+
Tested with Rails 2.3.18, 3.2.22, 4.1.14, 4.2.5 on Ruby 1.9.3, 2.2.4, 2.3.0 and JRuby 1.7, JRuby 9.0.5.0
|
43
43
|
|
44
44
|
Note, that Rails 3.2.14 associations are partly broken under JRuby cause of https://github.com/rails/rails/issues/11595
|
45
45
|
You'll need to upgrade activerecord-jdbc-adapter to >= 1.3.0.beta1, if you want to use this combination.
|
@@ -74,7 +74,7 @@ History
|
|
74
74
|
Most of the code for the habtm association was written by TylerRick for his gem [has_and_belongs_to_many_with_deferred_save](https://github.com/TylerRick/has_and_belongs_to_many_with_deferred_save)
|
75
75
|
Mainly, I changed two things:
|
76
76
|
|
77
|
-
* added ActiveRecord 3
|
77
|
+
* added compatibility for ActiveRecord 3 and 4
|
78
78
|
* removed singleton methods, because they interfere with caching
|
79
79
|
|
80
80
|
License
|
@@ -82,4 +82,4 @@ License
|
|
82
82
|
|
83
83
|
This plugin is licensed under the BSD license.
|
84
84
|
|
85
|
-
|
85
|
+
2016 (c) Martin Körner
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.8
|
@@ -1,61 +1,58 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
5
2
|
|
6
3
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version =
|
4
|
+
s.name = 'deferred_associations'
|
5
|
+
s.version = '0.5.8'
|
9
6
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(
|
11
|
-
s.authors = [
|
12
|
-
s.date =
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ['Martin Koerner', 'Tyler Rick', 'Alessio Caiazza']
|
9
|
+
s.date = '2016-03-08'
|
13
10
|
s.description = "Makes ActiveRecord defer/postpone saving the records you add to an habtm (has_and_belongs_to_many) or has_many\n association until you call model.save, allowing validation in the style of normal attributes. Additionally you\n can check inside before_save filters, if the association was altered."
|
14
|
-
s.email =
|
11
|
+
s.email = 'martin.koerner@objectfab.de'
|
12
|
+
s.licenses = 'MIT'
|
15
13
|
s.extra_rdoc_files = [
|
16
|
-
|
17
|
-
|
14
|
+
'CHANGELOG',
|
15
|
+
'Readme.markdown'
|
18
16
|
]
|
19
17
|
s.files = [
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
18
|
+
'CHANGELOG',
|
19
|
+
'Readme.markdown',
|
20
|
+
'VERSION',
|
21
|
+
'deferred_associations.gemspec',
|
22
|
+
'init.rb',
|
23
|
+
'lib/array_to_association_wrapper.rb',
|
24
|
+
'lib/deferred_associations.rb',
|
25
|
+
'lib/has_and_belongs_to_many_with_deferred_save.rb',
|
26
|
+
'lib/has_many_with_deferred_save.rb',
|
27
|
+
'spec/db/database.yml',
|
28
|
+
'spec/db/schema.rb',
|
29
|
+
'spec/habtm_ar4_spec.rb',
|
30
|
+
'spec/has_and_belongs_to_many_with_deferred_save_spec.rb',
|
31
|
+
'spec/has_many_with_deferred_save_spec.rb',
|
32
|
+
'spec/models/chair.rb',
|
33
|
+
'spec/models/door.rb',
|
34
|
+
'spec/models/person.rb',
|
35
|
+
'spec/models/room.rb',
|
36
|
+
'spec/models/table.rb',
|
37
|
+
'spec/spec_helper.rb'
|
40
38
|
]
|
41
|
-
s.homepage =
|
42
|
-
s.require_paths = [
|
43
|
-
s.rubygems_version =
|
44
|
-
s.summary =
|
39
|
+
s.homepage = 'http://github.com/MartinKoerner/deferred_associations'
|
40
|
+
s.require_paths = ['lib']
|
41
|
+
s.rubygems_version = '1.8.24'
|
42
|
+
s.summary = 'Makes ActiveRecord defer/postpone habtm or has_many associations'
|
45
43
|
|
46
|
-
if s.respond_to? :specification_version
|
44
|
+
if s.respond_to? :specification_version
|
47
45
|
s.specification_version = 3
|
48
46
|
|
49
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0')
|
50
|
-
s.add_runtime_dependency(
|
51
|
-
s.add_development_dependency(
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0')
|
48
|
+
s.add_runtime_dependency('activerecord', ['>= 0'])
|
49
|
+
s.add_development_dependency('rspec', ['>= 0'])
|
52
50
|
else
|
53
|
-
s.add_dependency(
|
54
|
-
s.add_dependency(
|
51
|
+
s.add_dependency('activerecord', ['>= 0'])
|
52
|
+
s.add_dependency('rspec', ['>= 0'])
|
55
53
|
end
|
56
54
|
else
|
57
|
-
s.add_dependency(
|
58
|
-
s.add_dependency(
|
55
|
+
s.add_dependency('activerecord', ['>= 0'])
|
56
|
+
s.add_dependency('rspec', ['>= 0'])
|
59
57
|
end
|
60
58
|
end
|
61
|
-
|
@@ -1,67 +1,68 @@
|
|
1
|
-
class ArrayToAssociationWrapper < Array
|
2
|
-
|
3
|
-
def defer_association_methods_to
|
4
|
-
@association_owner = owner
|
5
|
-
@association_name = association_name
|
6
|
-
end
|
7
|
-
|
8
|
-
# trick collection_name.include?(obj)
|
9
|
-
# If you use a collection of SingleTableInheritance and didn't :select 'type' the
|
10
|
-
# include? method will not find any subclassed object.
|
11
|
-
def include_with_deferred_save?(obj)
|
12
|
-
if @association_owner.present?
|
13
|
-
if
|
14
|
-
return true
|
15
|
-
else
|
16
|
-
return false
|
17
|
-
end
|
18
|
-
else
|
19
|
-
include_without_deferred_save?(obj)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
alias_method_chain :include?, 'deferred_save'
|
24
|
-
|
25
|
-
def find_with_deferred_save
|
26
|
-
if @association_owner.present?
|
27
|
-
collection_without_deferred_save.send(:find, *args)
|
28
|
-
else
|
29
|
-
find_without_deferred_save
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
alias_method_chain :find, :deferred_save
|
34
|
-
|
35
|
-
def first_with_deferred_save
|
36
|
-
if @association_owner.present?
|
37
|
-
collection_without_deferred_save.send(:first, *args)
|
38
|
-
else
|
39
|
-
first_without_deferred_save
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
alias_method_chain :first, :deferred_save
|
44
|
-
|
45
|
-
def last_with_deferred_save
|
46
|
-
if @association_owner.present?
|
47
|
-
collection_without_deferred_save.send(:last, *args)
|
48
|
-
else
|
49
|
-
last_without_deferred_save
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
alias_method_chain :last, :deferred_save
|
54
|
-
|
55
|
-
define_method :method_missing do |method, *args|
|
56
|
-
#puts "#{self.class}.method_missing(#{method}) (#{collection_without_deferred_save.inspect})"
|
57
|
-
if @association_owner.present?
|
58
|
-
collection_without_deferred_save.send(method, *args) unless method == :set_inverse_instance
|
59
|
-
else
|
60
|
-
super
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def collection_without_deferred_save
|
65
|
-
@association_owner.send("#{@association_name}_without_deferred_save")
|
66
|
-
end
|
67
|
-
|
1
|
+
class ArrayToAssociationWrapper < Array
|
2
|
+
|
3
|
+
def defer_association_methods_to(owner, association_name)
|
4
|
+
@association_owner = owner
|
5
|
+
@association_name = association_name
|
6
|
+
end
|
7
|
+
|
8
|
+
# trick collection_name.include?(obj)
|
9
|
+
# If you use a collection of SingleTableInheritance and didn't :select 'type' the
|
10
|
+
# include? method will not find any subclassed object.
|
11
|
+
def include_with_deferred_save?(obj)
|
12
|
+
if @association_owner.present?
|
13
|
+
if detect { |itm| itm == obj || (itm[:id] == obj[:id] && obj.is_a?(itm.class)) }
|
14
|
+
return true
|
15
|
+
else
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
else
|
19
|
+
include_without_deferred_save?(obj)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method_chain :include?, 'deferred_save'
|
24
|
+
|
25
|
+
def find_with_deferred_save(*args)
|
26
|
+
if @association_owner.present?
|
27
|
+
collection_without_deferred_save.send(:find, *args)
|
28
|
+
else
|
29
|
+
find_without_deferred_save
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method_chain :find, :deferred_save
|
34
|
+
|
35
|
+
def first_with_deferred_save(*args)
|
36
|
+
if @association_owner.present?
|
37
|
+
collection_without_deferred_save.send(:first, *args)
|
38
|
+
else
|
39
|
+
first_without_deferred_save
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method_chain :first, :deferred_save
|
44
|
+
|
45
|
+
def last_with_deferred_save(*args)
|
46
|
+
if @association_owner.present?
|
47
|
+
collection_without_deferred_save.send(:last, *args)
|
48
|
+
else
|
49
|
+
last_without_deferred_save
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
alias_method_chain :last, :deferred_save
|
54
|
+
|
55
|
+
define_method :method_missing do |method, *args|
|
56
|
+
# puts "#{self.class}.method_missing(#{method}) (#{collection_without_deferred_save.inspect})"
|
57
|
+
if @association_owner.present?
|
58
|
+
collection_without_deferred_save.send(method, *args) unless method == :set_inverse_instance
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def collection_without_deferred_save
|
65
|
+
@association_owner.send("#{@association_name}_without_deferred_save")
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
module ClassMethods
|
4
|
-
|
5
4
|
# Instructions:
|
6
5
|
#
|
7
6
|
# Replace your existing call to has_and_belongs_to_many with has_and_belongs_to_many_with_deferred_save.
|
@@ -17,7 +16,7 @@ module ActiveRecord
|
|
17
16
|
# end
|
18
17
|
def has_and_belongs_to_many_with_deferred_save(*args)
|
19
18
|
collection_name = args[0].to_s
|
20
|
-
collection_singular_ids = collection_name.singularize +
|
19
|
+
collection_singular_ids = collection_name.singularize + '_ids'
|
21
20
|
|
22
21
|
return if method_defined?("#{collection_name}_with_deferred_save")
|
23
22
|
|
@@ -29,44 +28,40 @@ module ActiveRecord
|
|
29
28
|
attr_accessor :"use_original_collection_reader_behavior_for_#{collection_name}"
|
30
29
|
|
31
30
|
define_method "#{collection_name}_with_deferred_save=" do |collection|
|
32
|
-
#puts "has_and_belongs_to_many_with_deferred_save: #{collection_name} = #{collection.collect(&:id).join(',')}"
|
33
|
-
|
31
|
+
# puts "has_and_belongs_to_many_with_deferred_save: #{collection_name} = #{collection.collect(&:id).join(',')}"
|
32
|
+
send "unsaved_#{collection_name}=", collection
|
34
33
|
end
|
35
34
|
|
36
|
-
define_method "#{collection_name}_with_deferred_save" do |*
|
37
|
-
if
|
38
|
-
|
35
|
+
define_method "#{collection_name}_with_deferred_save" do |*method_args|
|
36
|
+
if send("use_original_collection_reader_behavior_for_#{collection_name}")
|
37
|
+
send("#{collection_name}_without_deferred_save")
|
39
38
|
else
|
40
|
-
if
|
41
|
-
|
42
|
-
end
|
43
|
-
self.send("unsaved_#{collection_name}")
|
39
|
+
send("initialize_unsaved_#{collection_name}", *method_args) if send("unsaved_#{collection_name}").nil?
|
40
|
+
send("unsaved_#{collection_name}")
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
47
44
|
alias_method_chain :"#{collection_name}=", 'deferred_save'
|
48
45
|
alias_method_chain :"#{collection_name}", 'deferred_save'
|
49
46
|
|
50
|
-
define_method "#{collection_singular_ids}_with_deferred_save" do |*
|
51
|
-
if
|
52
|
-
|
47
|
+
define_method "#{collection_singular_ids}_with_deferred_save" do |*method_args|
|
48
|
+
if send("use_original_collection_reader_behavior_for_#{collection_name}")
|
49
|
+
send("#{collection_singular_ids}_without_deferred_save")
|
53
50
|
else
|
54
|
-
if
|
55
|
-
|
56
|
-
end
|
57
|
-
self.send("unsaved_#{collection_name}").map { |e| e[:id] }
|
51
|
+
send("initialize_unsaved_#{collection_name}", *method_args) if send("unsaved_#{collection_name}").nil?
|
52
|
+
send("unsaved_#{collection_name}").map { |e| e[:id] }
|
58
53
|
end
|
59
54
|
end
|
60
55
|
|
61
56
|
alias_method_chain :"#{collection_singular_ids}", 'deferred_save'
|
62
57
|
|
63
58
|
# only needed for ActiveRecord >= 3.0
|
64
|
-
if ActiveRecord::VERSION::STRING >=
|
59
|
+
if ActiveRecord::VERSION::STRING >= '3'
|
65
60
|
define_method "#{collection_singular_ids}_with_deferred_save=" do |ids|
|
66
|
-
ids = Array.wrap(ids).reject
|
67
|
-
reflection_wrapper =
|
61
|
+
ids = Array.wrap(ids).reject(&:blank?)
|
62
|
+
reflection_wrapper = send("#{collection_name}_without_deferred_save")
|
68
63
|
new_values = reflection_wrapper.klass.find(ids)
|
69
|
-
|
64
|
+
send("#{collection_name}=", new_values)
|
70
65
|
end
|
71
66
|
alias_method_chain :"#{collection_singular_ids}=", 'deferred_save'
|
72
67
|
end
|
@@ -82,62 +77,48 @@ module ActiveRecord
|
|
82
77
|
# But we only want the old behavior in this case -- most of the time we want the *new* behavior -- so we use
|
83
78
|
# @use_original_collection_reader_behavior as a switch.
|
84
79
|
|
85
|
-
|
86
|
-
if
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# /\ This is where the actual save occurs.
|
91
|
-
self.send "use_original_collection_reader_behavior_for_#{collection_name}=", false
|
80
|
+
send "use_original_collection_reader_behavior_for_#{collection_name}=", true
|
81
|
+
send("initialize_unsaved_#{collection_name}") if send("unsaved_#{collection_name}").nil?
|
82
|
+
send "#{collection_name}_without_deferred_save=", send("unsaved_#{collection_name}")
|
83
|
+
# /\ This is where the actual save occurs.
|
84
|
+
send "use_original_collection_reader_behavior_for_#{collection_name}=", false
|
92
85
|
|
93
86
|
true
|
94
87
|
end
|
95
88
|
after_save "do_#{collection_name}_save!"
|
96
89
|
|
97
|
-
|
98
|
-
define_method "reload_with_deferred_save_for_#{collection_name}" do |*args|
|
90
|
+
define_method "reload_with_deferred_save_for_#{collection_name}" do |*method_args|
|
99
91
|
# Reload from the *database*, discarding any unsaved changes.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
92
|
+
send("reload_without_deferred_save_for_#{collection_name}", *method_args).tap do
|
93
|
+
send "unsaved_#{collection_name}=", nil
|
94
|
+
# /\ If we didn't do this, then when we called reload, it would still have the same (possibly invalid) value of
|
95
|
+
# unsaved_collection that it had before the reload.
|
104
96
|
end
|
105
97
|
end
|
106
98
|
alias_method_chain :reload, "deferred_save_for_#{collection_name}"
|
107
99
|
|
100
|
+
define_method "initialize_unsaved_#{collection_name}" do |*method_args|
|
101
|
+
elements = send("#{collection_name}_without_deferred_save", *method_args)
|
108
102
|
|
109
|
-
|
110
|
-
#puts "Initialized to #{self.send("#{collection_name}_without_deferred_save").clone.inspect}"
|
111
|
-
elements = self.send("#{collection_name}_without_deferred_save", *args).clone
|
103
|
+
# here the association will be duped, so changes to "unsaved_#{collection_name}" will not be saved immediately
|
112
104
|
elements = ArrayToAssociationWrapper.new(elements)
|
113
105
|
elements.defer_association_methods_to self, collection_name
|
114
|
-
|
115
|
-
# /\ We initialize it to collection_without_deferred_save in case they just loaded the object from the
|
116
|
-
# database, in which case we want unsaved_collection to start out with the "saved collection".
|
117
|
-
# Actually, this doesn't clone the Association but the elements array instead (since the clone method is
|
118
|
-
# proxied like any other methods)
|
119
|
-
# Important: If we don't use clone, then it does an assignment by reference and any changes to unsaved_collection
|
120
|
-
# will also change *collection_without_deferred_save*! (Not what we want! Would result in us saving things
|
121
|
-
# immediately, which is exactly what we're trying to avoid.)
|
122
|
-
|
123
|
-
|
124
|
-
|
106
|
+
send "unsaved_#{collection_name}=", elements
|
125
107
|
end
|
126
108
|
private :"initialize_unsaved_#{collection_name}"
|
127
|
-
|
128
109
|
end
|
129
110
|
|
130
111
|
def add_deletion_callback
|
131
112
|
# this will delete all the association into the join table after obj.destroy,
|
132
113
|
# but is only useful/necessary, if the record is not paranoid?
|
133
|
-
unless
|
134
|
-
after_destroy
|
114
|
+
unless respond_to?(:paranoid?) && paranoid?
|
115
|
+
after_destroy do |record|
|
135
116
|
begin
|
136
117
|
record.save
|
137
118
|
rescue Exception => e
|
138
119
|
logger.warn "Association cleanup after destroy failed with #{e}"
|
139
120
|
end
|
140
|
-
|
121
|
+
end
|
141
122
|
end
|
142
123
|
end
|
143
124
|
end
|
@@ -1,113 +1,107 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
module ClassMethods
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
alias_method_chain :reload, "deferred_save_for_#{collection_name}"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
module ClassMethods
|
4
|
+
def has_many_with_deferred_save(*args)
|
5
|
+
collection_name = args[0].to_s
|
6
|
+
|
7
|
+
return if method_defined?("#{collection_name}_with_deferred_save")
|
8
|
+
|
9
|
+
has_many *args
|
10
|
+
|
11
|
+
if args[1].is_a?(Hash) && args[1].keys.include?(:through)
|
12
|
+
logger.warn "You are using the option :through on #{name}##{collection_name}. This was not tested very much with has_many_with_deferred_save. Please write many tests for your functionality!"
|
13
|
+
end
|
14
|
+
|
15
|
+
after_save "hmwds_update_#{collection_name}"
|
16
|
+
|
17
|
+
define_obj_setter collection_name
|
18
|
+
define_obj_getter collection_name
|
19
|
+
define_id_setter collection_name
|
20
|
+
define_id_getter collection_name
|
21
|
+
|
22
|
+
define_update_method collection_name
|
23
|
+
define_reload_method collection_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def define_obj_setter(collection_name)
|
27
|
+
define_method("#{collection_name}_with_deferred_save=") do |objs|
|
28
|
+
instance_variable_set "@hmwds_temp_#{collection_name}", objs || []
|
29
|
+
end
|
30
|
+
|
31
|
+
method_name = "#{collection_name}="
|
32
|
+
alias_method_chain method_name, :deferred_save
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_obj_getter(collection_name)
|
36
|
+
define_method("#{collection_name}_with_deferred_save") do
|
37
|
+
save_in_progress = instance_variable_get "@hmwds_#{collection_name}_save_in_progress"
|
38
|
+
|
39
|
+
# while updating the association, rails loads the association object - this needs to be the original one
|
40
|
+
unless save_in_progress
|
41
|
+
elements = instance_variable_get "@hmwds_temp_#{collection_name}"
|
42
|
+
if elements.nil?
|
43
|
+
elements = ArrayToAssociationWrapper.new(send("#{collection_name}_without_deferred_save"))
|
44
|
+
elements.defer_association_methods_to self, collection_name
|
45
|
+
instance_variable_set "@hmwds_temp_#{collection_name}", elements
|
46
|
+
end
|
47
|
+
|
48
|
+
result = elements
|
49
|
+
else
|
50
|
+
result = send("#{collection_name}_without_deferred_save")
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method_chain collection_name, :deferred_save
|
57
|
+
end
|
58
|
+
|
59
|
+
def define_id_setter(collection_name)
|
60
|
+
# only needed for ActiveRecord >= 3.0
|
61
|
+
if ActiveRecord::VERSION::STRING >= '3'
|
62
|
+
collection_singular_ids = "#{collection_name.singularize}_ids"
|
63
|
+
define_method "#{collection_singular_ids}_with_deferred_save=" do |ids|
|
64
|
+
ids = Array.wrap(ids).reject(&:blank?)
|
65
|
+
new_values = send("#{collection_name}_without_deferred_save").klass.find(ids)
|
66
|
+
send("#{collection_name}=", new_values)
|
67
|
+
end
|
68
|
+
alias_method_chain :"#{collection_singular_ids}=", 'deferred_save'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_id_getter(collection_name)
|
73
|
+
collection_singular_ids = "#{collection_name.singularize}_ids"
|
74
|
+
define_method "#{collection_singular_ids}_with_deferred_save" do
|
75
|
+
send(collection_name).map { |e| e[:id] }
|
76
|
+
end
|
77
|
+
alias_method_chain :"#{collection_singular_ids}", 'deferred_save'
|
78
|
+
end
|
79
|
+
|
80
|
+
def define_update_method(collection_name)
|
81
|
+
define_method "hmwds_update_#{collection_name}" do
|
82
|
+
unless frozen?
|
83
|
+
elements = instance_variable_get "@hmwds_temp_#{collection_name}"
|
84
|
+
unless elements.nil? # nothing has been done with the association
|
85
|
+
# save is done automatically, if original behaviour is restored
|
86
|
+
instance_variable_set "@hmwds_#{collection_name}_save_in_progress", true
|
87
|
+
send("#{collection_name}_without_deferred_save=", elements)
|
88
|
+
instance_variable_set "@hmwds_#{collection_name}_save_in_progress", false
|
89
|
+
|
90
|
+
instance_variable_set "@hmwds_temp_#{collection_name}", nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def define_reload_method(collection_name)
|
97
|
+
define_method "reload_with_deferred_save_for_#{collection_name}" do |*args|
|
98
|
+
# Reload from the *database*, discarding any unsaved changes.
|
99
|
+
send("reload_without_deferred_save_for_#{collection_name}", *args).tap do
|
100
|
+
instance_variable_set "@hmwds_temp_#{collection_name}", nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
alias_method_chain :reload, "deferred_save_for_#{collection_name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|