deferring 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: