deferring 0.0.10 → 0.1.0

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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- M2Q5NGQyOGY3MTViMzE1ODA4ODgwNzdjZTRmNDhjMjMyYWI4MDc3OA==
5
- data.tar.gz: !binary |-
6
- MDQ5ZTg4NTUyYmM3ZjgyNDM0NzI5ZTdhY2ZiMzMwMzkxM2M4NmM0Yw==
2
+ SHA1:
3
+ metadata.gz: 425c85b43ca58b47075b81fd98800f7ca2cb530e
4
+ data.tar.gz: 17649b71b066711330887c6a1170c2070baca519
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZmI0YTk4ZDIwOGZhNjFlMWViOTc3NGRmODhmYmE0YmI0NzgxZWM3ODI1ZGZl
10
- YmEwODQ3YjRlN2JlYzJmODY2MTI3Y2FhZjM5MTlkMjBjNWI4YjYwNDkxZTBh
11
- MTdhM2QxYzdhMjk1MGViM2JkOTQ0N2ZjZWRmYTlkYjAxNjVhOTU=
12
- data.tar.gz: !binary |-
13
- ZGE0NGE5ODhiYmQwMzMzMjg0ZmZmOTc0YTVlMjJmMjQxZGZkMjExYTJlNGI2
14
- ZmEzZmZhMmM0MGFmNjk4OTk2NmVkNzM0ZmUwMmUwMTEyZjc4ODhhZTM3MzRk
15
- MjU4NDdhNTE1OTIzYTVmMjE2NmNjYWFkOTIzOTNjMDQ4YWIyMTk=
6
+ metadata.gz: 4be333a99bf094965edf5f1636f84d039525d873844d4ba62ca75645676893d73ee1106948acd12b3f6302d231e441a323464663f2927e1b02a90fa1bc5bc709
7
+ data.tar.gz: 2c5d2de74c766c08956095a851a4f7405596327a5bc98e2ce9ad9e8b0d5b7b98f1b3040ec412423afe729e6f000c123293d36abe3e7a663bd5446d2c61c6bdef
data/README.md CHANGED
@@ -389,12 +389,9 @@ bundle exec appraisal rake
389
389
 
390
390
  ## TODO
391
391
 
392
- * add support for more Rubies
393
392
  * check out what is going on with uniq: true
394
393
  * collection(true) (same as reload)
395
394
  * collection.replace
396
- * validations!
397
- * validate: false does not work
398
395
 
399
396
  ## Contributing
400
397
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- deferring (0.0.9)
4
+ deferring (0.1.0)
5
5
  activerecord (> 3.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- deferring (0.0.9)
4
+ deferring (0.1.0)
5
5
  activerecord (> 3.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- deferring (0.0.9)
4
+ deferring (0.1.0)
5
5
  activerecord (> 3.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- deferring (0.0.9)
4
+ deferring (0.1.0)
5
5
  activerecord (> 3.0)
6
6
 
7
7
  GEM
data/lib/deferring.rb CHANGED
@@ -13,9 +13,15 @@ module Deferring
13
13
  def deferred_has_and_belongs_to_many(*args)
14
14
  options = args.extract_options!
15
15
  listeners = create_callback_listeners!(options)
16
+ autosave = options.fetch(:autosave, true)
17
+ validate = options.fetch(:validate, true)
16
18
 
17
19
  has_and_belongs_to_many(*args, options)
18
- generate_deferred_association_methods(args.first.to_s, listeners)
20
+ generate_deferred_association_methods(
21
+ args.first.to_s,
22
+ listeners,
23
+ autosave: autosave,
24
+ validate: validate)
19
25
  end
20
26
 
21
27
  # Creates a wrapper around `has_many`. A normal has many association is
@@ -26,9 +32,17 @@ module Deferring
26
32
  options = args.extract_options!
27
33
  listeners = create_callback_listeners!(options)
28
34
  inverse_association_name = options.fetch(:as, self.name.underscore.to_sym)
35
+ autosave = options.fetch(:autosave, true)
36
+ validate = options.fetch(:validate, true)
29
37
 
30
38
  has_many(*args, options)
31
- generate_deferred_association_methods(args.first.to_s, listeners, inverse_association_name)
39
+ generate_deferred_association_methods(
40
+ args.first.to_s,
41
+ listeners,
42
+ inverse_association_name: inverse_association_name,
43
+ autosave: autosave,
44
+ type: :has_many,
45
+ validate: validate)
32
46
  end
33
47
 
34
48
  def deferred_accepts_nested_attributes_for(*args)
@@ -122,7 +136,12 @@ module Deferring
122
136
 
123
137
  private
124
138
 
125
- def generate_deferred_association_methods(association_name, listeners, inverse_association_name = nil)
139
+ def generate_deferred_association_methods(association_name, listeners, options = {})
140
+ inverse_association_name = options[:inverse_association_name]
141
+ autosave = options.fetch(:autosave, true)
142
+ type = options.fetch(:type, :habtm)
143
+ validate = options.fetch(:validate, true)
144
+
126
145
  # Store the original accessor methods of the association.
127
146
  alias_method :"original_#{association_name}", :"#{association_name}"
128
147
  alias_method :"original_#{association_name}=", :"#{association_name}="
@@ -176,6 +195,40 @@ module Deferring
176
195
  send(:"#{association_name.singularize}_ids=", ids.split(','))
177
196
  end
178
197
 
198
+ after_validation :"perform_deferred_#{association_name}_validation!"
199
+ define_method :"perform_deferred_#{association_name}_validation!" do
200
+ find_or_create_deferred_association(association_name, listeners, inverse_association_name)
201
+
202
+ # Do not perform validations for HABTM associations as they are always
203
+ # validated by Rails upon saving.
204
+ return true if type == :habtm
205
+
206
+ # Do not perform validation when the association has not been loaded
207
+ # (performance improvement).
208
+ return true unless send(:"deferred_#{association_name}").loaded?
209
+
210
+ # Do not perform validations when validate: false.
211
+ return true if validate == false
212
+
213
+ all_records_valid = send(:"deferred_#{association_name}").objects.all? do |record|
214
+ unless valid = record.valid?
215
+ if autosave
216
+ record.errors.each do |attribute, message|
217
+ attribute = "#{association_name}.#{attribute}"
218
+ errors[attribute] << message
219
+ errors[attribute].uniq!
220
+ end
221
+ else
222
+ errors.add(association_name)
223
+ end
224
+ end
225
+ valid
226
+ end
227
+ return false unless all_records_valid
228
+
229
+ true
230
+ end
231
+
179
232
  # the save after the parent object has been saved
180
233
  after_save :"perform_deferred_#{association_name}_save!"
181
234
  define_method :"perform_deferred_#{association_name}_save!" do
@@ -44,7 +44,8 @@ module Deferring
44
44
  # Delegates methods from Ruby's Array module to the object in the deferred
45
45
  # association.
46
46
  delegate :[]=, :[], :clear, :select!, :reject!, :flatten, :flatten!, :sort!,
47
- :sort_by!, :empty?, :size, :length, to: :objects
47
+ :keep_if, :delete_if, :sort_by!, :empty?, :size, :length,
48
+ to: :objects
48
49
 
49
50
  # Delegates Ruby's Enumerable#find method to the original association.
50
51
  #
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Deferring
4
- VERSION = '0.0.10'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -35,8 +35,6 @@ RSpec.describe 'deferred has_many associations' do
35
35
  expect{ bob.save }.to change{ Person.find(bob.id).issues.size }.from(3).to(1)
36
36
  end
37
37
 
38
- xit 'does not create a link when parent is not valid'
39
-
40
38
  it 'replaces existing records when assigning a new set of records' do
41
39
  bob.issues = [db_issue]
42
40
 
@@ -51,6 +49,104 @@ RSpec.describe 'deferred has_many associations' do
51
49
  expect(bob.issues.first.person).to eq bob
52
50
  end
53
51
 
52
+ describe 'validations' do
53
+ xit 'does not create a link when parent is not valid'
54
+
55
+ context 'with invalid child and validate: true' do
56
+ it 'returns false when validating' do
57
+ bob.issues = [Issue.new]
58
+ expect(bob.valid?).to eq(false)
59
+ end
60
+
61
+ it 'returns false when saving' do
62
+ bob.issues = [Issue.new]
63
+ expect(bob.save).to eq(false)
64
+ end
65
+
66
+ it 'does not create a link' do
67
+ bob.issues = [Issue.new]
68
+ expect{ bob.save }.to_not change{ Person.find(bob.id).issues.size }
69
+ end
70
+ end
71
+
72
+ context 'with valid child and validate: true' do
73
+ it 'returns true when validating' do
74
+ bob.issues = [Issue.new(subject: 'Valid!')]
75
+ expect(bob.valid?).to eq(true)
76
+ end
77
+
78
+ it 'validates the child' do
79
+ bob.issues = [Issue.new]
80
+ bob.valid?
81
+ expect(bob.issues.first.validation_log).to eq([
82
+ 'Validating new issue'
83
+ ])
84
+ end
85
+
86
+ it 'returns true when saving' do
87
+ bob.issues = [Issue.new(subject: 'Valid!')]
88
+ expect(bob.save).to eq(true)
89
+ end
90
+
91
+ it 'creates a link' do
92
+ bob.issues = [Issue.new(subject: 'Valid!')]
93
+ expect{ bob.save }.to change{ Person.find(bob.id).issues.size }.from(0).to(1)
94
+ end
95
+ end
96
+
97
+ context 'with invalid child and validate: false' do
98
+ it 'returns true when validating' do
99
+ bob.non_validated_issues = [NonValidatedIssue.new]
100
+ expect(bob.valid?).to eq(true)
101
+ end
102
+
103
+ it 'does not validate the child' do
104
+ bob.non_validated_issues = [NonValidatedIssue.new]
105
+ bob.valid?
106
+ expect(bob.non_validated_issues.first.validation_log).to eq([])
107
+ end
108
+
109
+ unless rails30 # rails 3.0 does not return a error
110
+ it 'fails when trying to save the parent' do
111
+ bob.non_validated_issues = [NonValidatedIssue.new]
112
+
113
+ # Rails will raise the following error:
114
+ # - ActiveRecord::RecordNotSaved:
115
+ # Failed to replace non_validated_issues because one or more of the new records could not be saved.
116
+ #
117
+ # This behaviour is different from the default Rails behaviour.
118
+ # Rails will normally just save the parent and not save the
119
+ # association.
120
+ #
121
+ # Two ways to avoid this error (using the Deferring gem):
122
+ # - always use validate: true when user input is involved (e.g.
123
+ # using nested attributes to update the association),
124
+ # - add validations to the parent to check validness of the
125
+ # children when a child record can be valid on itself but invalid
126
+ # when added to the parent
127
+ expect{ bob.save }.to raise_error
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'with valid child and validate: false' do
133
+ it 'returns true when validating' do
134
+ bob.non_validated_issues = [NonValidatedIssue.new(subject: 'Valid!')]
135
+ expect(bob.valid?).to eq(true)
136
+ end
137
+
138
+ it 'returns true when saving' do
139
+ bob.non_validated_issues = [NonValidatedIssue.new(subject: 'Valid!')]
140
+ expect(bob.save).to eq(true)
141
+ end
142
+
143
+ it 'creates a link' do
144
+ bob.non_validated_issues = [NonValidatedIssue.new(subject: 'Valid!')]
145
+ expect{ bob.save }.to change{ Person.find(bob.id).non_validated_issues.size }.from(0).to(1)
146
+ end
147
+ end
148
+ end
149
+
54
150
  describe '#collection_singular_ids' do
55
151
  it 'returns ids of saved & unsaved associated records' do
56
152
  bob.issues = [printer_issue, db_issue]
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'support/active_record'
2
2
  require 'deferring'
3
- require 'support/models'
3
+ require 'support/models/person'
4
+ require 'support/models/team'
5
+ require 'support/models/issue'
6
+ require 'support/models/non_validated_issue'
4
7
  require 'support/rails_versions'
5
8
 
6
9
  RSpec.configure do |config|
@@ -24,5 +24,11 @@ ActiveRecord::Schema.define version: 0 do
24
24
  t.integer :person_id
25
25
  t.timestamps
26
26
  end
27
+
28
+ create_table :non_validated_issues do |t|
29
+ t.string :subject
30
+ t.integer :person_id
31
+ t.timestamps
32
+ end
27
33
  end
28
34
  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
@@ -0,0 +1,24 @@
1
+ class Issue < ActiveRecord::Base
2
+ belongs_to :person
3
+
4
+ validates_presence_of :subject
5
+ validate :dummy_validation
6
+
7
+ def validation_log
8
+ @validation_log ||= []
9
+ end
10
+
11
+ def log(validation_line)
12
+ validation_log << validation_line
13
+ validation_log
14
+ end
15
+
16
+ def dummy_validation
17
+ if new_record?
18
+ log("Validating new issue")
19
+ else
20
+ log("Validating issue #{id}")
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,24 @@
1
+ class NonValidatedIssue < ActiveRecord::Base
2
+ belongs_to :person
3
+
4
+ validates_presence_of :subject
5
+ validate :dummy_validation
6
+
7
+ def validation_log
8
+ @validation_log ||= []
9
+ end
10
+
11
+ def log(validation_line)
12
+ validation_log << validation_line
13
+ validation_log
14
+ end
15
+
16
+ def dummy_validation
17
+ if new_record?
18
+ log("Validating new non validated issue")
19
+ else
20
+ log("Validating non validated issue #{id}")
21
+ end
22
+ end
23
+
24
+ end
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  class Person < ActiveRecord::Base
4
2
 
5
3
  deferred_has_and_belongs_to_many :teams, before_link: :link_team,
@@ -17,6 +15,11 @@ class Person < ActiveRecord::Base
17
15
  after_remove: :removed_issue
18
16
  deferred_accepts_nested_attributes_for :issues, allow_destroy: true
19
17
 
18
+ deferred_has_many :non_validated_issues, before_remove: :remove_issue,
19
+ after_remove: :removed_issue,
20
+ validate: false
21
+
22
+
20
23
  validates_presence_of :name
21
24
 
22
25
  def audit_log
@@ -80,20 +83,3 @@ class Person < ActiveRecord::Base
80
83
  log("After removing issue #{issue.id}")
81
84
  end
82
85
  end
83
-
84
- class Team < ActiveRecord::Base
85
- has_and_belongs_to_many :people
86
-
87
- validates :name, presence: true
88
- validate :no_more_than_two_people_per_team
89
-
90
- def no_more_than_two_people_per_team
91
- if people.length > 2
92
- errors.add(:people, "A maximum of two persons per team is allowed")
93
- end
94
- end
95
- end
96
-
97
- class Issue < ActiveRecord::Base
98
- belongs_to :person
99
- end
@@ -0,0 +1,12 @@
1
+ class Team < ActiveRecord::Base
2
+ has_and_belongs_to_many :people
3
+
4
+ validates :name, presence: true
5
+ validate :no_more_than_two_people_per_team
6
+
7
+ def no_more_than_two_people_per_team
8
+ if people.length > 2
9
+ errors.add(:people, "A maximum of two persons per team is allowed")
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,100 +1,100 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deferring
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin Roestenburg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-10 00:00:00.000000000 Z
11
+ date: 2014-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>'
17
+ - - ">"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>'
24
+ - - ">"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: sqlite3
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ! '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ! '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: appraisal
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ! '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ! '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description: ! "\n The Deferring gem makes it possible to defer saving ActiveRecord\n
97
+ description: "\n The Deferring gem makes it possible to defer saving ActiveRecord\n
98
98
  \ associations until the parent object is saved.\n "
99
99
  email:
100
100
  - robin@roestenburg.io
@@ -102,9 +102,9 @@ executables: []
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - .gitignore
106
- - .rspec
107
- - .travis.yml
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
108
  - Appraisals
109
109
  - Gemfile
110
110
  - LICENSE.txt
@@ -128,7 +128,10 @@ files:
128
128
  - spec/lib/deferring_nested_attributes_spec.rb
129
129
  - spec/spec_helper.rb
130
130
  - spec/support/active_record.rb
131
- - spec/support/models.rb
131
+ - spec/support/models/issue.rb
132
+ - spec/support/models/non_validated_issue.rb
133
+ - spec/support/models/person.rb
134
+ - spec/support/models/team.rb
132
135
  - spec/support/rails_versions.rb
133
136
  homepage: http://github.com/robinroestenburg/deferring
134
137
  licenses:
@@ -140,17 +143,17 @@ require_paths:
140
143
  - lib
141
144
  required_ruby_version: !ruby/object:Gem::Requirement
142
145
  requirements:
143
- - - ! '>='
146
+ - - ">="
144
147
  - !ruby/object:Gem::Version
145
148
  version: '0'
146
149
  required_rubygems_version: !ruby/object:Gem::Requirement
147
150
  requirements:
148
- - - ! '>='
151
+ - - ">="
149
152
  - !ruby/object:Gem::Version
150
153
  version: '0'
151
154
  requirements: []
152
155
  rubyforge_project:
153
- rubygems_version: 2.4.1
156
+ rubygems_version: 2.2.2
154
157
  signing_key:
155
158
  specification_version: 4
156
159
  summary: Defer saving ActiveRecord associations until parent is saved
@@ -160,6 +163,9 @@ test_files:
160
163
  - spec/lib/deferring_nested_attributes_spec.rb
161
164
  - spec/spec_helper.rb
162
165
  - spec/support/active_record.rb
163
- - spec/support/models.rb
166
+ - spec/support/models/issue.rb
167
+ - spec/support/models/non_validated_issue.rb
168
+ - spec/support/models/person.rb
169
+ - spec/support/models/team.rb
164
170
  - spec/support/rails_versions.rb
165
171
  has_rdoc: