ncs_mdes_warehouse 0.1.1 → 0.2.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.
@@ -41,7 +41,10 @@ module NcsNavigator::Warehouse
41
41
 
42
42
  def unsuccessful_record(record, message)
43
43
  self.transform_errors <<
44
- TransformError.new(:model_class => record.class.name, :message => message)
44
+ TransformError.new(
45
+ :model_class => record.class.name,
46
+ :record_id => (record.key.first if record && record.key),
47
+ :message => message)
45
48
  end
46
49
  end
47
50
 
@@ -54,6 +57,7 @@ module NcsNavigator::Warehouse
54
57
  property :id, Serial
55
58
  property :message, Text, :required => true
56
59
  property :model_class, String, :length => 255
60
+ property :record_id, String, :length => 255
57
61
 
58
62
  belongs_to :transform_status, TransformStatus, :required => true
59
63
  end
@@ -37,16 +37,17 @@ module NcsNavigator::Warehouse::Transformers
37
37
  # @return [void]
38
38
  def transform(status)
39
39
  enum.each do |record|
40
+ apply_global_values_if_necessary(record)
40
41
  if record.valid?
41
42
  log.debug("Saving valid record #{record_ident record}.")
42
43
  begin
43
44
  unless record.save
44
- msg = "Could not save. #{record_ident(record)}."
45
+ msg = "Could not save."
45
46
  log.error msg
46
47
  status.unsuccessful_record(record, msg)
47
48
  end
48
49
  rescue => e
49
- msg = "Error on save. #{e.class}: #{e}. #{record_ident(record)}."
50
+ msg = "Error on save. #{e.class}: #{e}."
50
51
  log.error msg
51
52
  status.unsuccessful_record(record, msg)
52
53
  end
@@ -57,7 +58,7 @@ module NcsNavigator::Warehouse::Transformers
57
58
  "#{e} (#{prop}=#{v.inspect})."
58
59
  }
59
60
  }.flatten
60
- msg = "Invalid record. #{messages.join(' ')} #{record_ident(record)}."
61
+ msg = "Invalid record. #{messages.join(' ')}"
61
62
  log.error msg
62
63
  status.unsuccessful_record(record, msg)
63
64
  end
@@ -72,5 +73,19 @@ module NcsNavigator::Warehouse::Transformers
72
73
  '%s %s=%s' % [
73
74
  rec.class.name.demodulize, rec.class.key.first.name, rec.key.try(:first).inspect]
74
75
  end
76
+
77
+ def apply_global_values_if_necessary(record)
78
+ {
79
+ :psu_id => @configuration.navigator.psus.first.id,
80
+ :recruit_type => @configuration.navigator.recruitment_type_id
81
+ }.each do |attr, value|
82
+ setter = :"#{attr}="
83
+ if record.respond_to?(setter) && record.respond_to?(attr)
84
+ unless record.send(attr)
85
+ record.send(setter, value)
86
+ end
87
+ end
88
+ end
89
+ end
75
90
  end
76
91
  end
@@ -22,6 +22,7 @@ module NcsNavigator::Warehouse
22
22
  say clear_line_chars
23
23
  say s
24
24
  end
25
+ alias :clear_line_and_say :clear_line_then_say
25
26
 
26
27
  def say_line(s)
27
28
  say s, "\n"
@@ -1,5 +1,5 @@
1
1
  module NcsNavigator
2
2
  module Warehouse
3
- VERSION = '0.1.1'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -7,12 +7,28 @@ require 'fileutils'
7
7
  require 'forwardable'
8
8
 
9
9
  module NcsNavigator::Warehouse
10
+ ##
11
+ # Generates VDR XML from a warehouse instance. This is the object
12
+ # which implements the `emit-xml` tool in the `mdes-wh` command line
13
+ # client.
10
14
  class XmlEmitter
11
15
  extend Forwardable
12
16
 
17
+ ##
18
+ # @return [Configuration] the warehouse configuration used by this
19
+ # emitter.
13
20
  attr_reader :configuration
21
+
22
+ ##
23
+ # @return [Pathname] the file to which the XML will be emitted.
14
24
  attr_reader :filename
15
25
 
26
+ ##
27
+ # @return [Array<Models::MdesModel>] the models whose data will be
28
+ # emitted. This is determined from the `:tables` option to
29
+ # {#initialize}.
30
+ attr_reader :models
31
+
16
32
  def_delegators :@configuration, :shell, :log
17
33
 
18
34
  HEADER_TEMPLATE = ERB.new(<<-XML_ERB)
@@ -39,6 +55,10 @@ XML_ERB
39
55
  </ncs:recruitment_substudy_transmission_envelope>
40
56
  XML
41
57
 
58
+ ##
59
+ # @param configuration [Configuration]
60
+ # @return [Pathname] the default filename to use for a VDR XML
61
+ # submission. The format is `{county}-{YYYYMMDD}.xml`.
42
62
  def self.default_filename(configuration)
43
63
  psu_type = configuration.mdes.types.detect { |type| type.name =~ /^psu_cl/ }
44
64
  unless psu_type
@@ -48,7 +68,7 @@ XML
48
68
  psu_id = configuration.navigator.psus.first.id
49
69
  psu_entry = psu_type.code_list.detect { |cle| cle.value == psu_id }
50
70
  unless psu_entry
51
- fail "Cannot find PSU #{psu_id} in #{psu_type.name}. Please specify a filename manually"
71
+ fail "Cannot find PSU #{psu_id} in #{psu_type.name}. Please specify a filename manually."
52
72
  end
53
73
 
54
74
  Pathname.new '%s-%s.xml' % [
@@ -57,7 +77,26 @@ XML
57
77
  ]
58
78
  end
59
79
 
60
- def initialize(config, filename)
80
+ ##
81
+ # Create a new {XmlEmitter}.
82
+ #
83
+ # @param [Configuration] config the configuration for the
84
+ # warehouse from which to emit records.
85
+ # @param [Pathname,#to_s,nil] filename the filename to which the output
86
+ # will be written. If `nil`, the {.default_filename} is used.
87
+ #
88
+ # @option options [Fixnum] :block-size (5000) the maximum number
89
+ # of records to load into memory before writing them to the XML
90
+ # file. Reduce this to reduce the memory load of the emitter.
91
+ # Increasing it will probably not improve performance, even if
92
+ # you have sufficient memory to load more records.
93
+ # @option options [Boolean] :include-pii (false) should PII
94
+ # variable values be included in the XML?
95
+ # @option options [Array<#to_s>] :tables (all for current MDES
96
+ # version) the tables to include in the emitted XML.
97
+ # @option options [Boolean] :zip (true) should a ZIP file be
98
+ # produced alongside the XML file?
99
+ def initialize(config, filename, options={})
61
100
  @configuration = config
62
101
  @filename = case filename
63
102
  when Pathname
@@ -68,8 +107,23 @@ XML
68
107
  Pathname.new(filename.to_s)
69
108
  end
70
109
  @record_count = 0
110
+ @block_size = options[:'block-size'] || 5000
111
+ @zip = options.has_key?(:zip) ? options[:zip] : true
112
+ @include_pii = options[:'include-pii']
113
+ @models =
114
+ if options[:tables]
115
+ options[:tables].collect { |t| t.to_s }.collect { |t|
116
+ config.models_module.mdes_order.find { |model| model.mdes_table_name == t }
117
+ }
118
+ else
119
+ config.models_module.mdes_order
120
+ end
71
121
  end
72
122
 
123
+ ##
124
+ # Emit XML from the configured warehouse to {#filename}.
125
+ #
126
+ # @return [void]
73
127
  def emit_xml
74
128
  shell.say_line("Exporting to #{filename}")
75
129
  log.info("Beginning XML export to #{filename}")
@@ -78,7 +132,7 @@ XML
78
132
  filename.open('w') do |f|
79
133
  f.write HEADER_TEMPLATE.result(binding)
80
134
 
81
- configuration.models_module.mdes_order.each do |model|
135
+ models.each do |model|
82
136
  shell.clear_line_then_say('Writing XML for %33s' % model.mdes_table_name)
83
137
 
84
138
  write_all_xml_for_model(f, model)
@@ -91,14 +145,36 @@ XML
91
145
  shell.clear_line_then_say(msg)
92
146
  log.info(msg)
93
147
 
94
- shell.say_line("Zipping to #{zip_filename}")
95
- log.info("Zipping to #{zip_filename}")
96
- Zip::ZipFile.open(zip_filename, Zip::ZipFile::CREATE) do |zf|
97
- zf.add(filename.basename, filename)
148
+ if zip?
149
+ shell.say_line("Zipping to #{zip_filename}")
150
+ log.info("Zipping to #{zip_filename}")
151
+ Zip::ZipFile.open(zip_filename, Zip::ZipFile::CREATE) do |zf|
152
+ zf.add(filename.basename, filename)
153
+ end
154
+ log.info("XML export complete")
98
155
  end
99
- log.info("XML export complete")
100
156
  end
101
157
 
158
+ ##
159
+ # Will PII be included in the exported XML?
160
+ #
161
+ # @return [Boolean]
162
+ def include_pii?
163
+ @include_pii
164
+ end
165
+
166
+ ##
167
+ # Will a ZIP archive be created along with the XML?
168
+ #
169
+ # @return [Boolean]
170
+ def zip?
171
+ @zip
172
+ end
173
+
174
+ ##
175
+ # @return [Pathname] the filename for the ZIP archive of the XML,
176
+ # if any. Currently this is always {#filename} + '.zip'.
177
+ # @see #zip?
102
178
  def zip_filename
103
179
  @zip_filename ||= filename.to_s + '.zip'
104
180
  end
@@ -107,11 +183,17 @@ XML
107
183
 
108
184
  def write_all_xml_for_model(f, model)
109
185
  shell.say(' %20s' % '[loading]')
110
- model.all.each do |instance|
111
- instance.write_mdes_xml(f, :indent => 3, :margin => 1)
112
- @record_count += 1
113
-
114
- shell.back_up_and_say(20, '%5d (%5.1f/sec)' % [@record_count, emit_rate])
186
+ count = model.count
187
+ offset = 0
188
+ while offset < count
189
+ shell.back_up_and_say(20, '%20s' % '[loading]')
190
+ model.all(:limit => @block_size, :offset => offset).each do |instance|
191
+ instance.write_mdes_xml(f, :indent => 3, :margin => 1, :pii => include_pii?)
192
+ @record_count += 1
193
+
194
+ shell.back_up_and_say(20, '%5d (%5.1f/sec)' % [@record_count, emit_rate])
195
+ end
196
+ offset += @block_size
115
197
  end
116
198
  end
117
199
 
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
16
  s.require_paths = ["lib", "generated_models"]
17
17
 
18
- s.add_dependency 'ncs_mdes', '~> 0.4', '>= 0.4.2'
18
+ s.add_dependency 'ncs_mdes', '~> 0.5'
19
19
  s.add_dependency 'ncs_navigator_configuration', '~> 0.2'
20
20
 
21
21
  s.add_dependency 'activesupport', '~> 3.0'
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ module NcsNavigator::Warehouse::DataMapper
4
+ shared_examples 'a string-based NCS type' do
5
+ it 'converts a BigDecimal to a simple number string (no powers of ten)' do
6
+ subject.send(:typecast_to_primitive, BigDecimal.new('0.453E1')).should == '4.53'
7
+ end
8
+
9
+ it 'uses the default method for other types' do
10
+ subject.send(:typecast_to_primitive, 45).should == '45'
11
+ end
12
+ end
13
+
14
+ describe NcsString, :modifies_warehouse_state do
15
+ let(:model) {
16
+ class NcsStringHost
17
+ include DataMapper::Resource
18
+ property :haiku, NcsString
19
+ end
20
+ }
21
+
22
+ subject { model.properties[:haiku] }
23
+
24
+ it_behaves_like 'a string-based NCS type'
25
+ end
26
+
27
+ describe NcsText, :modifies_warehouse_state do
28
+ let(:model) {
29
+ class NcsTextHost
30
+ include DataMapper::Resource
31
+ property :novel, NcsText
32
+ end
33
+ }
34
+
35
+ subject { model.properties[:novel] }
36
+
37
+ it_behaves_like 'a string-based NCS type'
38
+ end
39
+ end
@@ -13,7 +13,9 @@ module NcsNavigator::Warehouse
13
13
  end
14
14
  end
15
15
 
16
- before(:all) do
16
+ before do
17
+ use_test_bcdatabase
18
+
17
19
  # Not config due to RSpec #500
18
20
  DatabaseInitializer.new(base_config).tap do |dbi|
19
21
  dbi.set_up_repository(:both)
@@ -48,7 +48,7 @@ module NcsNavigator::Warehouse::Transformers
48
48
  end
49
49
 
50
50
  describe '#connection_parameters' do
51
- describe 'from bcdatabase', :modifies_warehouse_state do
51
+ describe 'from bcdatabase', :modifies_warehouse_state, :use_test_bcdatabase do
52
52
  let(:cls) {
53
53
  sample_class do
54
54
  bcdatabase :name => 'ncs_staff_portal'
@@ -79,7 +79,7 @@ module NcsNavigator::Warehouse::Transformers
79
79
  end
80
80
  end
81
81
 
82
- describe '#repository' do
82
+ describe '#repository', :modifies_warehouse_state, :use_test_bcdatabase do
83
83
  include_context 'people_pro'
84
84
 
85
85
  let(:cls) do
@@ -97,7 +97,7 @@ module NcsNavigator::Warehouse::Transformers
97
97
  end
98
98
  end
99
99
 
100
- describe '#each' do
100
+ describe '#each', :modifies_warehouse_state, :use_test_bcdatabase do
101
101
  include_context 'people_pro'
102
102
 
103
103
  describe 'of .produce_records' do
@@ -5,6 +5,8 @@ module NcsNavigator::Warehouse::Transformers
5
5
  class Sample
6
6
  include ::DataMapper::Resource
7
7
 
8
+ property :psu_id, String
9
+ property :recruit_type, String
8
10
  property :id, Integer, :key => true
9
11
  property :name, String, :required => true
10
12
  end
@@ -14,7 +16,11 @@ module NcsNavigator::Warehouse::Transformers
14
16
  end
15
17
 
16
18
  describe '#transform' do
17
- let(:config) { NcsNavigator::Warehouse::Configuration.new }
19
+ let(:config) {
20
+ NcsNavigator::Warehouse::Configuration.new.tap do |c|
21
+ c.log_file = tmpdir + 'enum_transformer_test.log'
22
+ end
23
+ }
18
24
  let(:records) { [
19
25
  Sample.new(:id => 1, :name => 'One'),
20
26
  Sample.new(:id => 2, :name => 'Two'),
@@ -33,7 +39,29 @@ module NcsNavigator::Warehouse::Transformers
33
39
  transform_status.transform_errors.should be_empty
34
40
  end
35
41
 
36
- it 'automatically sets the PSU ID if the object accepts it'
42
+ it 'automatically sets the PSU ID if necessary' do
43
+ records[1].psu_id = '20000042'
44
+
45
+ records.each do |m|
46
+ m.should_receive(:valid?).and_return(true)
47
+ m.should_receive(:save).and_return(true)
48
+ end
49
+
50
+ subject.transform(transform_status)
51
+ records.collect(&:psu_id).should == %w(20000030 20000042 20000030)
52
+ end
53
+
54
+ it 'automatically sets recruit_type if necessary' do
55
+ records[2].recruit_type = '1'
56
+
57
+ records.each do |m|
58
+ m.should_receive(:valid?).and_return(true)
59
+ m.should_receive(:save).and_return(true)
60
+ end
61
+
62
+ subject.transform(transform_status)
63
+ records.collect(&:recruit_type).should == %w(3 3 1)
64
+ end
37
65
 
38
66
  describe 'with an invalid instance' do
39
67
  before do
@@ -47,7 +75,8 @@ module NcsNavigator::Warehouse::Transformers
47
75
  it 'records the invalid instance' do
48
76
  err = transform_status.transform_errors.first
49
77
  err.model_class.should == Sample.to_s
50
- err.message.should == 'Invalid record. Name must not be blank (name=nil). Sample id=3.'
78
+ err.record_id.should == '3'
79
+ err.message.should == 'Invalid record. Name must not be blank (name=nil).'
51
80
  end
52
81
 
53
82
  it 'saves the other instances' do
@@ -67,7 +96,8 @@ module NcsNavigator::Warehouse::Transformers
67
96
  it 'records the unsaveable instance' do
68
97
  err = transform_status.transform_errors.first
69
98
  err.model_class.should == Sample.to_s
70
- err.message.should == 'Could not save. Sample id=2.'
99
+ err.record_id.should == '2'
100
+ err.message.should == 'Could not save.'
71
101
  end
72
102
 
73
103
  it 'saves the saveable instances' do
@@ -87,8 +117,9 @@ module NcsNavigator::Warehouse::Transformers
87
117
  it 'records the failing instance' do
88
118
  err = transform_status.transform_errors.first
89
119
  err.model_class.should == Sample.to_s
120
+ err.record_id.should == '1'
90
121
  err.message.should ==
91
- 'Error on save. RuntimeError: No database around these parts. Sample id=1.'
122
+ 'Error on save. RuntimeError: No database around these parts.'
92
123
  end
93
124
 
94
125
  it 'saves the saveable instances' do
@@ -5,8 +5,9 @@ require 'zip/zip'
5
5
  module NcsNavigator::Warehouse
6
6
  describe XmlEmitter, :use_mdes do
7
7
  let(:filename) { tmpdir + 'export.xml' }
8
+ let(:options) { {} }
8
9
  let(:xml) {
9
- XmlEmitter.new(spec_config, filename).emit_xml
10
+ XmlEmitter.new(spec_config, filename, options).emit_xml
10
11
  Nokogiri::XML(File.read(filename))
11
12
  }
12
13
 
@@ -14,33 +15,91 @@ module NcsNavigator::Warehouse
14
15
  spec_config.models_module.const_get(:Person)
15
16
  end
16
17
 
18
+ def participant_model
19
+ spec_config.models_module.const_get(:Participant)
20
+ end
21
+
22
+ def stub_model(model)
23
+ model.stub!(:count).and_return(0)
24
+ end
25
+
17
26
  before do
18
- spec_config.models_module.mdes_order.each do |model|
19
- model.stub!(:all).and_return([])
27
+ spec_config.models_module.mdes_order.reject { |m|
28
+ [person_model, participant_model].include?(m)
29
+ }.each do |model|
30
+ stub_model(model)
20
31
  end
21
32
  end
22
33
 
23
34
  # Most of the details of the XML are tested on the MdesModel mixin
24
35
  describe 'the generated XML', :slow do
25
- it 'includes the SC from the configuration' do
26
- xml.xpath('//sc_id').text.should == '20000029'
36
+ describe 'global attributes' do
37
+ before do
38
+ stub_model(person_model)
39
+ stub_model(participant_model)
40
+ end
41
+
42
+ it 'includes the SC from the configuration' do
43
+ xml.xpath('//sc_id').text.should == '20000029'
44
+ end
45
+
46
+ it 'includes the PSU from the configuration' do
47
+ xml.xpath('//psu_id').text.should == '20000030'
48
+ end
49
+
50
+ it 'includes the appropriate specification version' do
51
+ xml.xpath('//specification_version').text.
52
+ should == spec_config.mdes.specification_version
53
+ end
54
+ end
55
+
56
+ def default_required_attributes(model)
57
+ model.properties.select { |prop| prop.required? }.inject({}) { |h, prop|
58
+ h[prop.name] = '-4'; h
59
+ }
60
+ end
61
+
62
+ def create_instance(model, attributes)
63
+ model.new(default_required_attributes(model).merge(attributes))
27
64
  end
28
65
 
29
- it 'includes the PSU from the configuration' do
30
- xml.xpath('//psu_id').text.should == '20000030'
66
+ def create_person(id, attributes={})
67
+ create_instance(person_model, { :person_id => id }.merge(attributes))
31
68
  end
32
69
 
33
- it 'includes the appropriate specification version' do
34
- xml.xpath('//specification_version').text.
35
- should == spec_config.mdes.specification_version
70
+ describe 'with exactly one actual record', :slow, :use_database do
71
+ let(:records) {
72
+ [
73
+ create_person('XQ4')
74
+ ]
75
+ }
76
+
77
+ before do
78
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
79
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
80
+ end
81
+
82
+ it 'contains records for all models' do
83
+ xml.xpath('//person').size.should == 1
84
+ end
85
+
86
+ it 'contains the right records' do
87
+ xml.xpath('//person/person_id').collect { |e| e.text.strip }.sort.should == %w(XQ4)
88
+ end
36
89
  end
37
90
 
38
- describe 'with actual data' do
91
+ describe 'with actual data', :slow, :use_database do
92
+ let(:records) {
93
+ [
94
+ create_person('XQ4', :first_name => 'Xavier'),
95
+ create_person('QX9', :first_name => 'Quentin'),
96
+ create_instance(participant_model, :p_id => 'P_QX4')
97
+ ]
98
+ }
99
+
39
100
  before do
40
- person_model.should_receive(:all).and_return([
41
- person_model.new(:person_id => 'XQ4'),
42
- person_model.new(:person_id => 'QX9')
43
- ])
101
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
102
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
44
103
  end
45
104
 
46
105
  it 'contains records for all models' do
@@ -48,7 +107,70 @@ module NcsNavigator::Warehouse
48
107
  end
49
108
 
50
109
  it 'contains the right records' do
51
- xml.xpath('//person/person_id').collect { |e| e.text.strip }.should == %w(XQ4 QX9)
110
+ xml.xpath('//person/person_id').collect { |e| e.text.strip }.sort.should == %w(QX9 XQ4)
111
+ end
112
+
113
+ describe 'and PII' do
114
+ let(:xml_first_names) {
115
+ xml.xpath('//person/first_name').collect { |e| e.text.strip }.sort
116
+ }
117
+
118
+ it 'excludes PII by default' do
119
+ xml_first_names.should == ['', '']
120
+ end
121
+
122
+ it 'excludes PII when explicitly excluded' do
123
+ options[:'include-pii'] = false
124
+ xml_first_names.should == ['', '']
125
+ end
126
+
127
+ it 'includes PII when requested' do
128
+ options[:'include-pii'] = true
129
+ xml_first_names.should == %w(Quentin Xavier)
130
+ end
131
+ end
132
+
133
+ describe 'and selected output' do
134
+ let(:people_count) { xml.xpath('//person').size }
135
+ let(:p_count) { xml.xpath('//participant').size }
136
+
137
+ it 'includes all tables by default' do
138
+ people_count.should == 2
139
+ p_count.should == 1
140
+ end
141
+
142
+ it 'includes only the selected tables when requested' do
143
+ options[:tables] = %w(participant)
144
+
145
+ people_count.should == 0
146
+ p_count.should == 1
147
+ end
148
+
149
+ it 'includes all the requested tables when explicitly requested' do
150
+ options[:tables] = spec_config.models_module.mdes_order.collect(&:mdes_table_name)
151
+
152
+ people_count.should == 2
153
+ p_count.should == 1
154
+ end
155
+ end
156
+ end
157
+
158
+ describe 'with lots and lots of actual data', :slow, :use_database do
159
+ let(:count) { 3134 }
160
+ let(:records) { (0...count).collect { |n| create_person(n) } }
161
+ let(:actual_ids) { xml.xpath('//person/person_id').collect { |e| e.text.strip } }
162
+
163
+ before do
164
+ pending 'Not working in CI at the moment' if ENV['CI_RUBY']
165
+ records.each { |rec| rec.save or fail "Save of #{rec.inspect} failed." }
166
+ end
167
+
168
+ it 'contains all the records' do
169
+ actual_ids.size.should == count
170
+ end
171
+
172
+ it 'contains the right records' do
173
+ actual_ids.collect(&:to_i).sort[2456].should == 2456
52
174
  end
53
175
  end
54
176
  end
@@ -57,27 +179,51 @@ module NcsNavigator::Warehouse
57
179
  let(:expected_zipfile) { Pathname.new(filename.to_s + '.zip') }
58
180
 
59
181
  before do
60
- xml # for side effects
182
+ stub_model(person_model)
183
+ stub_model(participant_model)
61
184
  end
62
185
 
63
- it 'exists' do
64
- expected_zipfile.should be_readable
65
- end
186
+ context do
187
+ def actual
188
+ xml
189
+ expected_zipfile
190
+ end
191
+
192
+ it 'exists by default' do
193
+ actual.should be_readable
194
+ end
66
195
 
67
- it 'contains just the XML file' do
68
- contents = []
69
- Zip::ZipFile.foreach(expected_zipfile) do |entry|
70
- contents << entry.name
196
+ it 'exists if explicitly requested' do
197
+ options[:zip] = true
198
+ actual.should be_readable
199
+ end
200
+
201
+ it 'does not exist when excluded' do
202
+ options[:zip] = false
203
+ actual.exist?.should be_false
71
204
  end
72
- contents.should == [ filename.basename.to_s ]
73
205
  end
74
206
 
75
- it 'can be opened by something other than rubyzip' do
76
- `which unzip`
77
- pending "unzip is not available" unless $? == 0
207
+ context do
208
+ before do
209
+ xml # for side effects
210
+ end
78
211
 
79
- `unzip -l '#{expected_zipfile}' 2>&1`.should =~ /#{filename.basename}\s/
80
- $?.should == 0
212
+ it 'contains just the XML file' do
213
+ contents = []
214
+ Zip::ZipFile.foreach(expected_zipfile) do |entry|
215
+ contents << entry.name
216
+ end
217
+ contents.should == [ filename.basename.to_s ]
218
+ end
219
+
220
+ it 'can be opened by something other than rubyzip' do
221
+ `which unzip`
222
+ pending "unzip is not available" unless $? == 0
223
+
224
+ `unzip -l '#{expected_zipfile}' 2>&1`.should =~ /#{filename.basename}\s/
225
+ $?.should == 0
226
+ end
81
227
  end
82
228
  end
83
229