granite-form 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -2
  3. data/.github/workflows/{ci.yml → ruby.yml} +22 -4
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +3 -3
  6. data/Appraisals +1 -2
  7. data/CHANGELOG.md +14 -0
  8. data/README.md +0 -2
  9. data/Rakefile +4 -0
  10. data/docker-compose.yml +14 -0
  11. data/gemfiles/rails.5.0.gemfile +0 -1
  12. data/gemfiles/rails.5.1.gemfile +0 -1
  13. data/gemfiles/rails.5.2.gemfile +0 -1
  14. data/granite-form.gemspec +16 -15
  15. data/lib/granite/form/active_record/associations.rb +1 -1
  16. data/lib/granite/form/base.rb +1 -2
  17. data/lib/granite/form/config.rb +10 -10
  18. data/lib/granite/form/errors.rb +0 -15
  19. data/lib/granite/form/model/associations/base.rb +0 -4
  20. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  21. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  22. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  23. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  24. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  25. data/lib/granite/form/model/associations/nested_attributes.rb +8 -8
  26. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  27. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  28. data/lib/granite/form/model/associations/references_many.rb +0 -32
  29. data/lib/granite/form/model/associations/references_one.rb +0 -28
  30. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
  33. data/lib/granite/form/model/associations/reflections/references_one.rb +3 -3
  34. data/lib/granite/form/model/associations/reflections/singular.rb +0 -8
  35. data/lib/granite/form/model/associations.rb +0 -6
  36. data/lib/granite/form/model/attributes/attribute.rb +1 -1
  37. data/lib/granite/form/model/attributes/base.rb +14 -17
  38. data/lib/granite/form/model/attributes/collection.rb +1 -1
  39. data/lib/granite/form/model/attributes/dictionary.rb +1 -1
  40. data/lib/granite/form/model/attributes/localized.rb +1 -1
  41. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  42. data/lib/granite/form/model/attributes/reference_one.rb +1 -9
  43. data/lib/granite/form/model/attributes/reflections/attribute.rb +0 -6
  44. data/lib/granite/form/model/attributes/reflections/base.rb +9 -12
  45. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -10
  46. data/lib/granite/form/model/persistence.rb +1 -19
  47. data/lib/granite/form/model/validations/nested.rb +1 -1
  48. data/lib/granite/form/model.rb +0 -2
  49. data/lib/granite/form/types/active_support/time_zone.rb +22 -0
  50. data/lib/granite/form/types/array.rb +17 -0
  51. data/lib/granite/form/types/big_decimal.rb +15 -0
  52. data/lib/granite/form/types/boolean.rb +38 -0
  53. data/lib/granite/form/types/date.rb +15 -0
  54. data/lib/granite/form/types/date_time.rb +15 -0
  55. data/lib/granite/form/types/float.rb +15 -0
  56. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
  57. data/lib/granite/form/types/integer.rb +13 -0
  58. data/lib/granite/form/types/object.rb +30 -0
  59. data/lib/granite/form/types/string.rb +13 -0
  60. data/lib/granite/form/types/time.rb +15 -0
  61. data/lib/granite/form/types/uuid.rb +22 -0
  62. data/lib/granite/form/types.rb +15 -0
  63. data/lib/granite/form/version.rb +1 -1
  64. data/lib/granite/form.rb +19 -118
  65. data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +16 -18
  66. data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
  67. data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
  68. data/spec/granite/form/extensions_spec.rb +12 -0
  69. data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +29 -305
  70. data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +27 -212
  71. data/spec/granite/form/model/associations/nested_attributes_spec.rb +23 -0
  72. data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
  73. data/spec/granite/form/model/associations/references_many_spec.rb +251 -0
  74. data/spec/granite/form/model/associations/references_one_spec.rb +173 -0
  75. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  76. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  77. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  78. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +1 -1
  79. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +1 -22
  80. data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -3
  81. data/spec/{lib/granite → granite}/form/model/associations_spec.rb +3 -24
  82. data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
  83. data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
  84. data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
  85. data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
  86. data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
  87. data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
  88. data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
  89. data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
  90. data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
  91. data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
  92. data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
  93. data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
  94. data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
  95. data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
  96. data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +1 -1
  97. data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -2
  98. data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
  99. data/spec/{lib/granite → granite}/form/model/representation_spec.rb +0 -0
  100. data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
  101. data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +2 -4
  102. data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +57 -15
  103. data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
  104. data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
  105. data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
  106. data/spec/granite/form/types/array_spec.rb +13 -0
  107. data/spec/granite/form/types/big_decimal_spec.rb +19 -0
  108. data/spec/granite/form/types/boolean_spec.rb +21 -0
  109. data/spec/granite/form/types/date_spec.rb +18 -0
  110. data/spec/granite/form/types/date_time_spec.rb +20 -0
  111. data/spec/granite/form/types/float_spec.rb +19 -0
  112. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
  113. data/spec/granite/form/types/integer_spec.rb +18 -0
  114. data/spec/granite/form/types/object_spec.rb +40 -0
  115. data/spec/granite/form/types/string_spec.rb +13 -0
  116. data/spec/granite/form/types/time_spec.rb +31 -0
  117. data/spec/granite/form/types/uuid_spec.rb +21 -0
  118. data/spec/{lib/granite → granite}/form_spec.rb +0 -0
  119. data/spec/spec_helper.rb +0 -15
  120. data/spec/support/active_record.rb +20 -0
  121. data/spec/{shared → support/shared}/nested_attribute_examples.rb +3 -21
  122. data/spec/support/shared/type_examples.rb +7 -0
  123. metadata +173 -123
  124. data/.github/workflows/main.yml +0 -29
  125. data/gemfiles/rails.4.2.gemfile +0 -15
  126. data/lib/granite/form/model/callbacks.rb +0 -72
  127. data/lib/granite/form/model/lifecycle.rb +0 -309
  128. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +0 -119
  129. data/spec/lib/granite/form/model/associations/references_many_spec.rb +0 -572
  130. data/spec/lib/granite/form/model/associations/references_one_spec.rb +0 -445
  131. data/spec/lib/granite/form/model/callbacks_spec.rb +0 -337
  132. data/spec/lib/granite/form/model/lifecycle_spec.rb +0 -356
  133. data/spec/lib/granite/form/model/typecasting_spec.rb +0 -193
@@ -1,309 +0,0 @@
1
- module Granite
2
- module Form
3
- module Model
4
- # == Lifecycle methods for Granite::Form::Model
5
- #
6
- # Provides methods +save+ and +destroy+ and its bang variants.
7
- # Also, patches +create+ and +update_attributes+ methods by adding
8
- # save at the end.
9
- #
10
- # You can define save or destroy performers with <tt>define_<action></tt>
11
- # methods. Create and update performers might be defined instead of
12
- # save performer:
13
- #
14
- # class Book
15
- # include Granite::Form::Model
16
- # include Granite::Form::Model::Lifecycle
17
- #
18
- # attribute :id, Integer
19
- # attribute :title, String
20
- #
21
- # define_save do # executes in the instance scope
22
- # REDIS.set(id, attributes.to_json)
23
- # end
24
- #
25
- # define_destroy do
26
- # REDIS.del(id)
27
- # end
28
- # end
29
- #
30
- # class Author
31
- # include Granite::Form::Model
32
- # include Granite::Form::Model::Lifecycle
33
- #
34
- # attribute :id, Integer
35
- # attribute :name, String
36
- #
37
- # define_create do # will be called on create only
38
- # REDIS.sadd('author_ids', id)
39
- # REDIS.set(id, attributes.to_json)
40
- # end
41
- #
42
- # define_update do # will be called on update only
43
- # REDIS.set(id, attributes.to_json)
44
- # end
45
- # end
46
- #
47
- # In case of undefined performer Granite::Form::UnsavableObject
48
- # or Granite::Form::UndestroyableObject will be raised respectively.
49
- #
50
- # If performers was not defined in model, they cat be passed as
51
- # blocks to `save`, `update` and `destroy` methods:
52
- #
53
- # authos.save { REDIS.set(id, attributes.to_json) }
54
- # authos.update { REDIS.set(id, attributes.to_json) }
55
- # authos.destroy { REDIS.del(id) }
56
- #
57
- # Save and destroy processes acts almost the save way as
58
- # ActiveRecord's (with +persisted?+ and +destroyed?+ methods
59
- # affecting).
60
- #
61
- module Lifecycle
62
- extend ActiveSupport::Concern
63
-
64
- included do
65
- include Persistence
66
-
67
- class_attribute(*%i[save create update destroy].map { |action| "_#{action}_performer" })
68
- private(*%i[save create update destroy].map { |action| "_#{action}_performer=" })
69
- end
70
-
71
- module ClassMethods
72
- # <tt>define_<action></tt> methods define performers for lifecycle
73
- # actions. Every action block must return boolean result, which
74
- # would mean the action success. If action performed unsuccessfully
75
- # Granite::Form::ObjectNotSaved or Granite::Form::ObjectNotDestroyed will
76
- # be raised respectively in case of bang methods using.
77
- #
78
- # class Author
79
- # define_create { true }
80
- # end
81
- #
82
- # Author.new.save # => true
83
- # Author.new.save! # => true
84
- #
85
- # class Author
86
- # define_create { false }
87
- # end
88
- #
89
- # Author.new.save # => false
90
- # Author.new.save! # => Granite::Form::ObjectNotSaved
91
- #
92
- # Also performers blocks are executed in the instance context, but
93
- # instance also passed as argument
94
- #
95
- # define_update do |instance|
96
- # instance.attributes.to_json
97
- # end
98
- #
99
- # +define_create+ and +define_update+ performers has higher priority
100
- # than +define_save+.
101
- #
102
- # class Author
103
- # define_update { ... }
104
- # define_save { ... }
105
- # end
106
- #
107
- # author = Author.create # using define_save performer
108
- # author.update_attributes(...) # using define_update performer
109
- #
110
- %i[save create update destroy].each do |action|
111
- define_method "define_#{action}" do |&block|
112
- send("_#{action}_performer=", block)
113
- end
114
- end
115
-
116
- # Initializes new instance with attributes passed and calls +save+
117
- # on it. Returns instance in any case.
118
- #
119
- def create(*args)
120
- new(*args).tap(&:save)
121
- end
122
-
123
- # Initializes new instance with attributes passed and calls +save!+
124
- # on it. Returns instance in case of success and raises Granite::Form::ValidationError
125
- # or Granite::Form::ObjectNotSaved in case of validation or saving fail respectively.
126
- #
127
- def create!(*args)
128
- new(*args).tap(&:save!)
129
- end
130
- end
131
-
132
- # <tt>define_<action></tt> on instance level works the same
133
- # way as class <tt>define_<action></tt> methods, but defines
134
- # performers for instance only
135
- #
136
- # user.define_save do
137
- # REDIS.set(id, attributes.to_json)
138
- # end
139
- # user.save! # => will use instance-level performer
140
- #
141
- %i[save create update destroy].each do |action|
142
- define_method "define_#{action}" do |&block|
143
- send("_#{action}_performer=", block)
144
- end
145
- end
146
-
147
- # Assigns passed attributes and calls +save+
148
- # Returns true or false in case of successful or unsuccessful
149
- # saving respectively.
150
- #
151
- # author.update(name: 'Donald')
152
- #
153
- # If update performer is not defined with `define_update`
154
- # or `define_save`, it raises Granite::Form::UnsavableObject.
155
- # Also save performer block might be passed instead of in-class
156
- # performer definition:
157
- #
158
- # author.update(name: 'Donald') { REDIS.set(id, attributes.to_json) }
159
- #
160
- def update(attributes, &block)
161
- assign_attributes(attributes) && save(&block)
162
- end
163
-
164
- alias_method :update_attributes, :update
165
-
166
- # Assigns passed attributes and calls +save!+
167
- # Returns true in case of success and raises Granite::Form::ValidationError
168
- # or Granite::Form::ObjectNotSaved in case of validation or
169
- # saving fail respectively.
170
- #
171
- # author.update!(name: 'Donald')
172
- #
173
- # If update performer is not defined with `define_update`
174
- # or `define_save`, it raises Granite::Form::UnsavableObject.
175
- # Also save performer block might be passed instead of in-class
176
- # performer definition:
177
- #
178
- # author.update!(name: 'Donald') { REDIS.set(id, attributes.to_json) }
179
- #
180
- def update!(attributes, &block)
181
- assign_attributes(attributes) && save!(&block)
182
- end
183
-
184
- alias_method :update_attributes!, :update!
185
-
186
- # # Saves object by calling save performer defined with +define_save+,
187
- # +define_create+ or +define_update+ methods.
188
- # Returns true or false in case of successful
189
- # or unsuccessful saving respectively. Changes +persisted?+ to true
190
- #
191
- # author.save
192
- #
193
- # If save performer is not defined with `define_update` or
194
- # `define_create` or `define_save`, it raises Granite::Form::UnsavableObject.
195
- # Also save performer block might be passed instead of in-class
196
- # performer definition:
197
- #
198
- # author.save { REDIS.set(id, attributes.to_json) }
199
- #
200
- def save(_options = {}, &block)
201
- raise Granite::Form::UnsavableObject unless block || savable?
202
- valid? && save_object(&block)
203
- end
204
-
205
- # Saves object by calling save performer defined with +define_save+,
206
- # +define_create+ or +define_update+ methods.
207
- # Returns true in case of success and raises Granite::Form::ValidationError
208
- # or Granite::Form::ObjectNotSaved in case of validation or
209
- # saving fail respectively. Changes +persisted?+ to true
210
- #
211
- # author.save!
212
- #
213
- # If save performer is not defined with `define_update` or
214
- # `define_create` or `define_save`, it raises Granite::Form::UnsavableObject.
215
- # Also save performer block might be passed instead of in-class
216
- # performer definition:
217
- #
218
- # author.save! { REDIS.set(id, attributes.to_json) }
219
- #
220
- def save!(_options = {}, &block)
221
- raise Granite::Form::UnsavableObject unless block || savable?
222
- validate!
223
- save_object(&block) or raise Granite::Form::ObjectNotSaved
224
- end
225
-
226
- # Destroys object by calling the destroy performer.
227
- # Returns instance in any case. Changes +persisted?+
228
- # to false and +destroyed?+ to true in case of success.
229
- #
230
- # author.destroy
231
- #
232
- # If destroy performer is not defined with `define_destroy`,
233
- # it raises Granite::Form::UndestroyableObject.
234
- # Also destroy performer block might be passed instead of in-class
235
- # performer definition:
236
- #
237
- # author.destroy { REDIS.del(id) }
238
- #
239
- def destroy(&block)
240
- raise Granite::Form::UndestroyableObject unless block || destroyable?
241
- destroy_object(&block)
242
- self
243
- end
244
-
245
- # Destroys object by calling the destroy performer.
246
- # In case of success returns instance and changes +persisted?+
247
- # to false and +destroyed?+ to true.
248
- # Raises Granite::Form::ObjectNotDestroyed in case of fail.
249
- #
250
- # author.destroy!
251
- #
252
- # If destroy performer is not defined with `define_destroy`,
253
- # it raises Granite::Form::UndestroyableObject.
254
- # Also destroy performer block might be passed instead of in-class
255
- # performer definition:
256
- #
257
- # author.destroy! { REDIS.del(id) }
258
- #
259
- def destroy!(&block)
260
- raise Granite::Form::UndestroyableObject unless block || destroyable?
261
- destroy_object(&block) or raise Granite::Form::ObjectNotDestroyed
262
- self
263
- end
264
-
265
- private
266
-
267
- def savable?
268
- !!((persisted? ? _update_performer : _create_performer) || _save_performer)
269
- end
270
-
271
- def save_object(&block)
272
- apply_association_changes! if respond_to?(:apply_association_changes!)
273
- result = persisted? ? update_object(&block) : create_object(&block)
274
- mark_persisted! if result
275
- result
276
- end
277
-
278
- def create_object(&block)
279
- performer = block || _create_performer || _save_performer
280
- !!performer_exec(&performer)
281
- end
282
-
283
- def update_object(&block)
284
- performer = block || _update_performer || _save_performer
285
- !!performer_exec(&performer)
286
- end
287
-
288
- def destroyable?
289
- !!_destroy_performer
290
- end
291
-
292
- def destroy_object(&block)
293
- performer = block || _destroy_performer
294
- result = !!performer_exec(&performer)
295
- mark_destroyed! if result
296
- result
297
- end
298
-
299
- def performer_exec(&block)
300
- if block.arity == 1
301
- yield(self)
302
- else
303
- instance_exec(&block)
304
- end
305
- end
306
- end
307
- end
308
- end
309
- end
@@ -1,119 +0,0 @@
1
- require 'spec_helper'
2
- require 'shared/nested_attribute_examples'
3
-
4
- describe Granite::Form::Model::Associations::NestedAttributes do
5
- context '' do
6
- before do
7
- stub_model :user do
8
- include Granite::Form::Model::Associations
9
-
10
- attribute :email, String
11
- embeds_one :profile
12
- embeds_many :projects
13
-
14
- accepts_nested_attributes_for :profile, :projects
15
-
16
- def save
17
- apply_association_changes!
18
- end
19
- end
20
- end
21
-
22
- include_examples 'nested attributes'
23
- end
24
-
25
- xcontext 'references_one' do
26
- before do
27
- stub_class(:author, ActiveRecord::Base)
28
- stub_class(:user, ActiveRecord::Base)
29
-
30
- stub_model :book do
31
- include Granite::Form::Model::Associations
32
-
33
- references_one :author
34
- references_many :users
35
-
36
- accepts_nested_attributes_for :author, :users
37
- end
38
- end
39
-
40
- context 'references_one' do
41
- let(:book) { Book.new }
42
-
43
- specify { expect { book.author_attributes = {} }.to change { book.author }.to(an_instance_of(Author)) }
44
- specify { expect { book.author_attributes = {name: 'Author'} }.to change { book.author.try(:name) }.to('Author') }
45
- specify { expect { book.author_attributes = {id: 42, name: 'Author'} }.to raise_error Granite::Form::ObjectNotFound }
46
-
47
- context ':reject_if' do
48
- context do
49
- before { Book.accepts_nested_attributes_for :author, reject_if: :all_blank }
50
- specify { expect { book.author_attributes = {name: ''} }.not_to change { book.author } }
51
- end
52
-
53
- context do
54
- before { Book.accepts_nested_attributes_for :author, reject_if: ->(attributes) { attributes['name'].blank? } }
55
- specify { expect { book.author_attributes = {name: ''} }.not_to change { book.author } }
56
- end
57
- end
58
-
59
- context 'existing' do
60
- let(:author) { Author.new(name: 'Author') }
61
- let(:book) { Book.new author: author }
62
-
63
- specify { expect { book.author_attributes = {id: 42, name: 'Author'} }.to raise_error Granite::Form::ObjectNotFound }
64
- specify { expect { book.author_attributes = {id: author.id.to_s, name: 'Author 1'} }.to change { book.author.name }.to('Author 1') }
65
- specify { expect { book.author_attributes = {name: 'Author 1'} }.to change { book.author.name }.to('Author 1') }
66
- specify { expect { book.author_attributes = {name: 'Author 1', _destroy: '1'} }.not_to change { book.author.name } }
67
- specify do
68
- expect do
69
- book.author_attributes = {name: 'Author 1', _destroy: '1'}
70
- book.save { true }
71
- end.not_to change { book.author.name }
72
- end
73
- specify { expect { book.author_attributes = {id: author.id.to_s, name: 'Author 1', _destroy: '1'} }.to change { book.author.name }.to('Author 1') }
74
- specify do
75
- expect do
76
- book.author_attributes = {id: author.id.to_s, name: 'Author 1', _destroy: '1'}
77
- book.save { true }
78
- end.to change { book.author.name }.to('Author 1')
79
- end
80
-
81
- context ':allow_destroy' do
82
- before { Book.accepts_nested_attributes_for :author, allow_destroy: true }
83
-
84
- specify { expect { book.author_attributes = {name: 'Author 1', _destroy: '1'} }.not_to change { book.author.name } }
85
- specify do
86
- expect do
87
- book.author_attributes = {name: 'Author 1', _destroy: '1'}
88
- book.save { true }
89
- end.not_to change { book.author.name }
90
- end
91
- specify { expect { book.author_attributes = {id: author.id.to_s, name: 'Author 1', _destroy: '1'} }.to change { book.author.name }.to('Author 1') }
92
- specify do
93
- expect do
94
- book.author_attributes = {id: author.id.to_s, name: 'Author 1', _destroy: '1'}
95
- book.save { true }
96
- end.to change { book.author }.to(nil)
97
- end
98
- end
99
-
100
- context ':update_only' do
101
- before { Book.accepts_nested_attributes_for :author, update_only: true }
102
-
103
- specify do
104
- expect { book.author_attributes = {id: 42, name: 'Author 1'} }
105
- .to change { book.author.name }.to('Author 1')
106
- end
107
- end
108
- end
109
- end
110
-
111
- context 'references_many' do
112
- let(:book) { Book.new }
113
- end
114
- end
115
-
116
- describe '#assign_attributes' do
117
- specify 'invent a good example'
118
- end
119
- end