csv_model 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.
@@ -0,0 +1,297 @@
1
+ require 'spec_helper'
2
+
3
+ describe CSVModel::ObjectWithStatusSnapshot do
4
+ let(:model) { double("model", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: false) }
5
+ let(:subject) { described_class.new(model) }
6
+
7
+ describe "#assign_attributes" do
8
+ it "does not raise when model is nil" do
9
+ subject = described_class.new(nil)
10
+ expect { subject.assign_attributes({}) }.to_not raise_exception
11
+ end
12
+ end
13
+
14
+ describe "#errors" do
15
+ it "does not raise when model is nil" do
16
+ subject = described_class.new(nil)
17
+ expect { subject.errors }.to_not raise_exception
18
+ end
19
+
20
+ it "returns an error when model is nil" do
21
+ subject = described_class.new(nil)
22
+ expect(subject.errors).to eq(["Record could not be created or updated"])
23
+ end
24
+
25
+ it "delegates to errors when model is not nil" do
26
+ model = double("model", errors: :some_errors)
27
+ subject = described_class.new(model)
28
+ expect(subject.errors).to eq(:some_errors)
29
+ end
30
+
31
+ it "uses to errors#full_messages when available" do
32
+ errors = double(full_messages: :some_errors)
33
+ model = double("model", errors: errors)
34
+ subject = described_class.new(model)
35
+ expect(subject.errors).to eq(:some_errors)
36
+ end
37
+ end
38
+
39
+ describe "#mark_as_duplicate" do
40
+ describe "internals" do
41
+ it "sets is_duplicate flag to true" do
42
+ subject.mark_as_duplicate
43
+ expect(subject.instance_variable_get("@is_duplicate")).to eq(true)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#save" do
49
+ context "with an invalid model" do
50
+ let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: true, valid?: false) }
51
+
52
+ it "does not calls save on underlying model" do
53
+ expect(model).to_not receive(:save)
54
+ subject.save
55
+ end
56
+ end
57
+
58
+ context "on a normal-run with an editable, valid model" do
59
+ let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
60
+
61
+ it "calls save on underlying model" do
62
+ expect(model).to receive(:save)
63
+ subject.save
64
+ end
65
+ end
66
+
67
+ context "on a normal-run with an editable, valid model that is marked for destruction" do
68
+ let(:model) { double("model", changed?: true, marked_for_destruction?: true, new_record?: false, valid?: true) }
69
+
70
+ it "calls destroy on underlying model" do
71
+ expect(model).to receive(:destroy)
72
+ subject.save
73
+ end
74
+ end
75
+
76
+ describe "internals" do
77
+ before do
78
+ allow(model).to receive(:valid?).and_return(true)
79
+ allow(model).to receive(:save).and_return(true)
80
+ subject.save
81
+ end
82
+
83
+ it "sets was_saved flag to true when editable and valid" do
84
+ allow(model).to receive(:valid?).and_return(true)
85
+ subject.save(dry_run: true)
86
+ expect(subject.instance_variable_get("@was_saved")).to eq(true)
87
+ end
88
+
89
+ it "sets was_saved flag to false when not editable" do
90
+ allow(model).to receive(:editable?).and_return(false)
91
+ allow(model).to receive(:valid?).and_return(true)
92
+ subject.save(dry_run: true)
93
+ expect(subject.instance_variable_get("@was_saved")).to eq(false)
94
+ end
95
+
96
+ it "sets was_saved flag to false when not valid" do
97
+ allow(model).to receive(:valid?).and_return(false)
98
+ subject.save(dry_run: true)
99
+ expect(subject.instance_variable_get("@was_saved")).to eq(false)
100
+ end
101
+
102
+ it "capture dry run flag to false" do
103
+ expect(subject.instance_variable_get("@is_dry_run")).to eq(false)
104
+ end
105
+
106
+ it "captures changed flag" do
107
+ expect(subject.instance_variable_get("@was_changed")).to eq(false)
108
+ end
109
+
110
+ it "captures deleted flag" do
111
+ expect(subject.instance_variable_get("@was_deleted")).to eq(false)
112
+ end
113
+
114
+ it "captures editable flag" do
115
+ expect(subject.instance_variable_get("@was_editable")).to eq(true)
116
+ end
117
+
118
+ it "captures new flag" do
119
+ expect(subject.instance_variable_get("@was_new")).to eq(false)
120
+ end
121
+
122
+ it "captures save flag" do
123
+ expect(subject.instance_variable_get("@was_saved")).to eq(true)
124
+ end
125
+
126
+ it "captures valid flag" do
127
+ expect(subject.instance_variable_get("@was_valid")).to eq(true)
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "#status" do
133
+ it "is ERROR_ON_READ when model is nil" do
134
+ subject = described_class.new(nil)
135
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_READ)
136
+ end
137
+
138
+ context "during a dry-run" do
139
+ it "is DUPLICATE when record is marked as a duplicate" do
140
+ subject.mark_as_duplicate
141
+ subject.save(dry_run: true)
142
+ expect(subject.status).to eq(CSVModel::RecordStatus::DUPLICATE)
143
+ end
144
+
145
+ it "is NOT_CHANGED when record exists and was not changed" do
146
+ subject.save(dry_run: true)
147
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
148
+ end
149
+
150
+ it "is CREATE when record was new and valid" do
151
+ expect(model).to receive(:new_record?).and_return(true)
152
+ expect(model).to receive(:valid?).and_return(true)
153
+ subject.save(dry_run: true)
154
+ expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
155
+ end
156
+
157
+ it "is ERROR_ON_CREATE when record was new and not valid" do
158
+ expect(model).to receive(:new_record?).and_return(true)
159
+ subject.save(dry_run: true)
160
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
161
+ end
162
+
163
+ it "is DELETE when record exists and is marked for destruction" do
164
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
165
+ subject.save(dry_run: true)
166
+ expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
167
+ end
168
+
169
+ it "is ERROR_ON_DELETE when record was new and marked for destruction" do
170
+ expect(model).to receive(:new_record?).and_return(true)
171
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
172
+ subject.save(dry_run: true)
173
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
174
+ end
175
+
176
+ it "is UPDATE when record exists, was changed and was valid" do
177
+ expect(model).to receive(:changed?).and_return(true)
178
+ expect(model).to receive(:valid?).and_return(true)
179
+ subject.save(dry_run: true)
180
+ expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
181
+ end
182
+
183
+ it "is ERROR_ON_UPDATE when record exists but was changed and not editable" do
184
+ expect(model).to receive(:changed?).and_return(true)
185
+ expect(model).to receive(:editable?).and_return(false)
186
+ subject.save(dry_run: true)
187
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
188
+ end
189
+
190
+ it "is ERROR_ON_UPDATE when record exists, was changed and was not valid" do
191
+ expect(model).to receive(:changed?).and_return(true)
192
+ subject.save(dry_run: true)
193
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
194
+ end
195
+ end
196
+
197
+ context "during normal processing" do
198
+ it "is not DUPLICATE when record is marked as a duplicate" do
199
+ subject.mark_as_duplicate
200
+ subject.save
201
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
202
+ end
203
+
204
+ it "is NOT_CHANGED when record exists and is not changed" do
205
+ subject.save
206
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
207
+ end
208
+
209
+ it "is CREATE when record is new, valid and saves" do
210
+ expect(model).to receive(:new_record?).and_return(true)
211
+ expect(model).to receive(:valid?).and_return(true)
212
+ expect(model).to receive(:save).and_return(true)
213
+ subject.save
214
+ expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
215
+ end
216
+
217
+ it "is ERROR_ON_CREATE when record is new and not valid" do
218
+ expect(model).to receive(:new_record?).and_return(true)
219
+ subject.save
220
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
221
+ end
222
+
223
+ it "is DELETE when record exists, is marked for destruction and saves" do
224
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
225
+ subject.save
226
+ expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
227
+ end
228
+
229
+ it "is ERROR_ON_DELETE when record is new and marked for destruction" do
230
+ expect(model).to receive(:new_record?).and_return(true)
231
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
232
+ subject.save
233
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
234
+ end
235
+
236
+ it "is UPDATE when record exists, is changed, valid and saves" do
237
+ expect(model).to receive(:changed?).and_return(true)
238
+ expect(model).to receive(:save).and_return(true)
239
+ expect(model).to receive(:valid?).and_return(true)
240
+ subject.save
241
+ expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
242
+ end
243
+
244
+ it "is ERROR_ON_UPDATE when record exists, is changed and is not valid" do
245
+ expect(model).to receive(:changed?).and_return(true)
246
+ subject.save
247
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
248
+ end
249
+
250
+ it "is ERROR_ON_UPDATE when record exists, is changed, is valid and does not save" do
251
+ expect(model).to receive(:changed?).and_return(true)
252
+ subject.save
253
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
254
+ end
255
+ end
256
+ end
257
+
258
+ describe "#valid?" do
259
+ it "does not raise when model is nil" do
260
+ subject = described_class.new(nil)
261
+ expect { subject.valid? }.to_not raise_exception
262
+ end
263
+ end
264
+
265
+ describe "internals" do
266
+ describe "#is_duplicate?" do
267
+ it "doesn't respond to is_duplicate?" do
268
+ expect(subject.respond_to?(:is_duplicate?)).to eq(false)
269
+ end
270
+
271
+ it "defaults to nil" do
272
+ expect(subject.send(:is_duplicate?)).to eq(nil)
273
+ end
274
+
275
+ it "displays @is_duplicate" do
276
+ subject.instance_variable_set("@is_duplicate", true)
277
+ expect(subject.send(:is_duplicate?)).to eq(true)
278
+ end
279
+ end
280
+
281
+ describe "#was_saved?" do
282
+ it "doesn't respond to was_saved?" do
283
+ expect(subject.respond_to?(:was_saved?)).to eq(false)
284
+ end
285
+
286
+ it "defaults to nil" do
287
+ expect(subject.send(:was_saved?)).to eq(nil)
288
+ end
289
+
290
+ it "displays @was_saved" do
291
+ subject.instance_variable_set("@was_saved", true)
292
+ expect(subject.send(:was_saved?)).to eq(true)
293
+ end
294
+ end
295
+ end
296
+
297
+ end
@@ -0,0 +1,351 @@
1
+ require 'spec_helper'
2
+
3
+ describe CSVModel::Row do
4
+
5
+ let(:header) { double("header") }
6
+ let(:column) { double("column", key: "column one", model_attribute: :column_one, name: "Column One") }
7
+ let(:columns) { [column] }
8
+ let(:primary_key_columns) { [] }
9
+
10
+ let(:data) { ["Value One"] }
11
+ let(:subject) { described_class.new(header, data) }
12
+
13
+ before do
14
+ allow(header).to receive(:columns).and_return(columns)
15
+ allow(header).to receive(:column_index).and_return(nil)
16
+ allow(header).to receive(:column_index).with("column one").and_return(0)
17
+ allow(header).to receive(:primary_key_columns).and_return(primary_key_columns)
18
+ end
19
+
20
+ describe "#errors" do
21
+ context "when no errors" do
22
+ let(:model) { double("model") }
23
+ let(:subject) { described_class.new(header, data, model: model) }
24
+ let(:model_instance) { double("model-instance", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: true) }
25
+
26
+ before do
27
+ allow(model).to receive(:find_row_model).and_return(model_instance)
28
+ allow(model_instance).to receive(:save)
29
+ end
30
+
31
+ it "returns an empty array when no errors" do
32
+ expect(subject.errors).to eq([])
33
+ end
34
+ end
35
+
36
+ context "when subject is a duplicate" do
37
+ before do
38
+ subject.mark_as_duplicate
39
+ end
40
+
41
+ context "during normal processing" do
42
+ it "does not return an error" do
43
+ expect(subject.errors).to_not include("Duplicate row")
44
+ end
45
+ end
46
+
47
+ context "during a dry-run" do
48
+ let(:subject) { described_class.new(header, data, dry_run: true) }
49
+
50
+ it "returns an error" do
51
+ expect(subject.errors).to include("Duplicate row")
52
+ end
53
+
54
+ context "when the row has a primary key" do
55
+ let(:primary_key_columns) { [column] }
56
+
57
+ it "returns an error specific to the column" do
58
+ expect(subject.errors).to include("Duplicate Column One")
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when the model instance has errors" do
65
+ let(:model) { double("model") }
66
+ let(:subject) { described_class.new(header, data, model: model) }
67
+ let(:model_instance) { double("model-instance", changed?: false, errors: errors, marked_for_destruction?: false, new_record?: false, valid?: false) }
68
+ let(:errors) { ["Message one", "Message two"] }
69
+
70
+ before do
71
+ allow(model).to receive(:find_row_model).and_return(model_instance)
72
+ end
73
+
74
+ it "includes the model instance errors" do
75
+ expect(subject.errors).to eq(errors)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "#index, #[]" do
81
+ it "returns nil when index is before than first element" do
82
+ expect(subject.index(-1)).to eq(nil)
83
+ expect(subject[-1]).to eq(nil)
84
+ end
85
+
86
+ it "returns nil when index is after than the last element" do
87
+ expect(subject.index(data.size)).to eq(nil)
88
+ expect(subject[data.size]).to eq(nil)
89
+ end
90
+
91
+ it "returns the value at the specified index" do
92
+ expect(subject.index(0)).to eq("Value One")
93
+ expect(subject[0]).to eq("Value One")
94
+ end
95
+
96
+ it "returns the value for the specified column key" do
97
+ expect(subject.index("column one")).to eq("Value One")
98
+ expect(subject["column one"]).to eq("Value One")
99
+ end
100
+
101
+ it "returns nil when the specified column key not exist" do
102
+ expect(subject.index("column two")).to eq(nil)
103
+ expect(subject["column two"]).to eq(nil)
104
+ end
105
+ end
106
+
107
+ describe "#key" do
108
+ it "returns the entire row if no primary key is defined" do
109
+ expect(subject.key).to eq(data)
110
+ end
111
+
112
+ context "when the row has a single primary key column" do
113
+ let(:primary_key_columns) { [column] }
114
+
115
+ it "returns the value for the key column" do
116
+ expect(subject.key).to eq(data.first)
117
+ end
118
+ end
119
+
120
+ context "when the row has a composite key" do
121
+ let(:columns) { [
122
+ double("column", key: "column one"),
123
+ double("column", key: "column two"),
124
+ double("column", key: "column three")
125
+ ] }
126
+ let(:primary_key_columns) { [
127
+ double("column", key: "column one"),
128
+ double("column", key: "column two")
129
+ ] }
130
+ let(:data) { ["Value One", "Value Two", "Value Three"] }
131
+
132
+ before do
133
+ allow(header).to receive(:column_index).with("column two").and_return(1)
134
+ end
135
+
136
+ it "returns the value for the key columns" do
137
+ expect(subject.key).to eq(["Value One", "Value Two"])
138
+ end
139
+ end
140
+ end
141
+
142
+ describe "#mark_as_duplicate" do
143
+ it "returns true" do
144
+ expect(subject.mark_as_duplicate).to eq(true)
145
+ end
146
+ end
147
+
148
+ describe "#marked_as_duplicate?" do
149
+ it "returns false when not marked as duplicate" do
150
+ expect(subject.marked_as_duplicate?).to eq(false)
151
+ end
152
+
153
+ it "returns true when marked as duplicate" do
154
+ subject.mark_as_duplicate
155
+ expect(subject.marked_as_duplicate?).to eq(true)
156
+ end
157
+ end
158
+
159
+ describe "#status" do
160
+ let(:model) { double("model") }
161
+ let(:wrapper) { double("wrapper", status: :some_status) }
162
+
163
+ before do
164
+ allow(subject).to receive(:find).and_return(model)
165
+ allow(wrapper).to receive(:assign_attributes)
166
+ allow(wrapper).to receive(:save)
167
+ end
168
+
169
+ it "is delegates status to the model" do
170
+ expect(CSVModel::ObjectWithStatusSnapshot).to receive(:new).and_return(wrapper)
171
+ expect(subject.status).to eq(:some_status)
172
+ end
173
+ end
174
+
175
+ describe "#valid?" do
176
+ it "returns true if no errors" do
177
+ expect(subject).to receive(:errors).and_return([])
178
+ expect(subject.valid?).to eq(true)
179
+ end
180
+
181
+ it "returns false if errors" do
182
+ expect(subject).to receive(:errors).and_return(["error"])
183
+ expect(subject.valid?).to eq(false)
184
+ end
185
+ end
186
+
187
+ describe "internals" do
188
+ describe "#all_attributes" do
189
+ it "doesn't respond to all_attributes" do
190
+ expect(subject.respond_to?(:all_attributes)).to eq(false)
191
+ end
192
+
193
+ it "returns all attributes and values" do
194
+ expect(subject.send(:all_attributes)).to eq(column_one: "Value One")
195
+ end
196
+
197
+ context "with multiple columns" do
198
+ let(:columns) { [
199
+ double("column", key: "column one", model_attribute: :column_one),
200
+ double("column", key: "column two", model_attribute: :column_two),
201
+ double("column", key: "column three", model_attribute: :column_three)
202
+ ] }
203
+ let(:data) { ["Value One", "", "Value Three"] }
204
+
205
+ before do
206
+ allow(header).to receive(:column_index).with("column two").and_return(1)
207
+ allow(header).to receive(:column_index).with("column three").and_return(2)
208
+ end
209
+
210
+ it "returns all attributes and values" do
211
+ expect(subject.send(:all_attributes)).to eq({ column_one: "Value One", column_two: "", column_three: "Value Three" })
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "#inherit_or_delegate" do
217
+ let(:model) { double("model") }
218
+ let(:subject) { described_class.new(header, data, model: model) }
219
+
220
+ it "doesn't respond to inherit_or_delegate" do
221
+ expect(subject.respond_to?(:inherit_or_delegate)).to eq(false)
222
+ end
223
+
224
+ it "returns nil when no method invoked" do
225
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(nil)
226
+ end
227
+
228
+ it "invokes internal method if method is defined" do
229
+ allow(subject).to receive(:respond_to?).with(:some_method).and_return(true)
230
+ allow(subject).to receive(:respond_to?).with(:some_method, false).and_return(true)
231
+ expect(subject).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
232
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
233
+ end
234
+
235
+ it "invokes delegate method if delegate exists and internal method is not defined" do
236
+ expect(model).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
237
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
238
+ end
239
+ end
240
+
241
+ describe "#key_attributes" do
242
+ it "doesn't respond to key_attributes" do
243
+ expect(subject.respond_to?(:key_attributes)).to eq(false)
244
+ end
245
+
246
+ it "returns the entire row if no primary key is defined" do
247
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
248
+ end
249
+
250
+ context "when the row has a single primary key column" do
251
+ let(:primary_key_columns) { [column] }
252
+
253
+ it "returns the value for the key column" do
254
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
255
+ end
256
+ end
257
+
258
+ context "when the row has a composite key" do
259
+ let(:columns) { [
260
+ double("column", key: "column one", model_attribute: :column_one),
261
+ double("column", key: "column two", model_attribute: :column_two),
262
+ double("column", key: "column three", model_attribute: :column_three)
263
+ ] }
264
+ let(:primary_key_columns) { [
265
+ double("column", key: "column one", model_attribute: :column_one),
266
+ double("column", key: "column two", model_attribute: :column_two)
267
+ ] }
268
+ let(:data) { ["Value One", "Value Two", "Value Three"] }
269
+
270
+ before do
271
+ allow(header).to receive(:column_index).with("column two").and_return(1)
272
+ end
273
+
274
+ it "returns the value for the key columns" do
275
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One", column_two: "Value Two")
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "#model_instance" do
281
+ let(:model) { double("model") }
282
+ let(:model_instance) { double("model-instance") }
283
+ let(:subject) { described_class.new(header, data, model: model) }
284
+
285
+ before do
286
+ allow(subject).to receive(:key_attributes).and_return(:key_attributes)
287
+ end
288
+
289
+ it "doesn't respond to model_instance" do
290
+ expect(subject.respond_to?(:model_instance)).to eq(false)
291
+ end
292
+
293
+ it "first tries to find an instance via #find_row_model" do
294
+ allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
295
+ allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
296
+ expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
297
+ expect(subject.send(:model_instance)).to eq(model_instance)
298
+ end
299
+
300
+ it "tries to find an instance via model#find_row_model when #find_row_model does not exist" do
301
+ expect(model).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
302
+ expect(subject.send(:model_instance)).to eq(model_instance)
303
+ end
304
+
305
+ it "tries to instantiate a new model via #new_row_model when a model cannot be found" do
306
+ allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
307
+ allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
308
+ allow(subject).to receive(:respond_to?).with(:new_row_model).and_return(true)
309
+ allow(subject).to receive(:respond_to?).with(:new_row_model, false).and_return(true)
310
+ expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(nil)
311
+ expect(subject).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
312
+ expect(subject.send(:model_instance)).to eq(model_instance)
313
+ end
314
+
315
+ it "tries to instantiate a new model via model#new_row_model when a model cannot be found and #new_row_model does not exist" do
316
+ expect(model).to receive(:find_row_model).with(:key_attributes).and_return(nil)
317
+ expect(model).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
318
+ expect(subject.send(:model_instance)).to eq(model_instance)
319
+ end
320
+ end
321
+
322
+ describe "#process_row" do
323
+ let(:model) { double("model") }
324
+ let(:model_instance) { double("model-instance", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
325
+ let(:subject) { described_class.new(header, data, model: model) }
326
+
327
+ before do
328
+ allow(model).to receive(:find_row_model).and_return(model_instance)
329
+ allow(subject).to receive(:all_attributes).and_return(:all_attributes)
330
+ end
331
+
332
+ it "doesn't respond to process_row" do
333
+ expect(subject.respond_to?(:process_row)).to eq(false)
334
+ end
335
+
336
+ it "assigns attributes and saves the model instance" do
337
+ expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
338
+ expect(model_instance).to receive(:save)
339
+ subject.send(:process_row)
340
+ end
341
+
342
+ it "only processes a record once" do
343
+ expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
344
+ expect(model_instance).to receive(:save)
345
+ subject.send(:process_row)
346
+ subject.send(:process_row)
347
+ end
348
+ end
349
+ end
350
+
351
+ end