deferred_associations 0.5.7 → 0.5.8
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.
- 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
|