importable_attachments 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/.gitignore +24 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +38 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +8 -0
  7. data/README.rdoc +3 -0
  8. data/Rakefile +29 -0
  9. data/app/assets/images/importable_attachments/.gitkeep +0 -0
  10. data/app/assets/images/importable_attachments/buttons/.htaccess +5 -0
  11. data/app/assets/images/importable_attachments/buttons/download_32.png +0 -0
  12. data/app/assets/images/importable_attachments/buttons/upload_32.png +0 -0
  13. data/app/assets/javascripts/importable_attachments/application.js +14 -0
  14. data/app/assets/javascripts/importable_attachments/attachments.coffee +41 -0
  15. data/app/assets/stylesheets/importable_attachments/application.css +14 -0
  16. data/app/assets/stylesheets/importable_attachments/attachments.css +4 -0
  17. data/app/assets/stylesheets/scaffold.css +56 -0
  18. data/app/controllers/importable_attachments/application_controller.rb +4 -0
  19. data/app/controllers/importable_attachments/attachments_controller.rb +190 -0
  20. data/app/controllers/importable_attachments/versions_controller.rb +87 -0
  21. data/app/helpers/importable_attachments/application_helper.rb +4 -0
  22. data/app/models/attachment.rb +24 -0
  23. data/app/models/importable_attachments/attachment.rb +143 -0
  24. data/app/models/importable_attachments/version.rb +50 -0
  25. data/app/validators/existing_class_validator.rb +17 -0
  26. data/app/validators/importable_attachments/csv_validator.rb +36 -0
  27. data/app/validators/importable_attachments/excel.rb +18 -0
  28. data/app/validators/importable_attachments/excel_validator.rb +18 -0
  29. data/app/views/importable_attachments/attachments/_attachment.html.haml +9 -0
  30. data/app/views/importable_attachments/attachments/_form.html.haml +22 -0
  31. data/app/views/importable_attachments/attachments/_nested_form.html.haml +20 -0
  32. data/app/views/importable_attachments/attachments/edit.html.haml +39 -0
  33. data/app/views/importable_attachments/attachments/index.html.haml +23 -0
  34. data/app/views/importable_attachments/attachments/index.xml.builder +23 -0
  35. data/app/views/importable_attachments/attachments/new.html.haml +10 -0
  36. data/app/views/importable_attachments/attachments/show.html.haml +43 -0
  37. data/app/views/importable_attachments/versions/_form.html.haml +25 -0
  38. data/app/views/importable_attachments/versions/edit.html.haml +7 -0
  39. data/app/views/importable_attachments/versions/index.html.haml +27 -0
  40. data/app/views/importable_attachments/versions/new.html.haml +5 -0
  41. data/app/views/importable_attachments/versions/show.html.haml +21 -0
  42. data/app/views/layouts/_version.html.haml +33 -0
  43. data/app/views/layouts/importable_attachments/application.html.haml +48 -0
  44. data/bin/set_lc.sh +47 -0
  45. data/config/database.yml +25 -0
  46. data/config/features/attachments.rb +8 -0
  47. data/config/features/mark_requirements.rb +3 -0
  48. data/config/features/smarter_dates.rb +3 -0
  49. data/config/features/versioning.rb +7 -0
  50. data/config/initializers/0_configuration.rb +7 -0
  51. data/config/initializers/formtastic.rb +76 -0
  52. data/config/initializers/generators.rb +10 -0
  53. data/config/initializers/paperclip.rb +27 -0
  54. data/config/locales/responders.en.yml +10 -0
  55. data/config/routes.rb +11 -0
  56. data/db/migrate/001_create_importable_attachments_versions.rb +14 -0
  57. data/db/migrate/100_create_attachments.rb +19 -0
  58. data/importable_attachments.gemspec +81 -0
  59. data/lib/generators/importable_attachments/install_generator.rb +66 -0
  60. data/lib/generators/importable_attachments/templates/features/attachments.rb.erb +7 -0
  61. data/lib/generators/importable_attachments/templates/features/versioning.rb +7 -0
  62. data/lib/generators/importable_attachments/templates/initializers/paperclip.rb +27 -0
  63. data/lib/importable_attachments/base.rb +108 -0
  64. data/lib/importable_attachments/blueprints.rb +9 -0
  65. data/lib/importable_attachments/engine.rb +8 -0
  66. data/lib/importable_attachments/importers/csv.rb +208 -0
  67. data/lib/importable_attachments/importers/excel.rb +37 -0
  68. data/lib/importable_attachments/importers.rb +7 -0
  69. data/lib/importable_attachments/version.rb +3 -0
  70. data/lib/importable_attachments.rb +9 -0
  71. data/lib/paperclip_processors/save_upload.rb +33 -0
  72. data/lib/tasks/importable_attachments_tasks.rake +4 -0
  73. data/script/rails +8 -0
  74. data/spec/attachments/books.csv +6 -0
  75. data/spec/attachments/books.txt +6 -0
  76. data/spec/attachments/books2.csv +4 -0
  77. data/spec/attachments/empty.csv +0 -0
  78. data/spec/attachments/failed_instances.csv +3 -0
  79. data/spec/attachments/invalid_headers.csv +3 -0
  80. data/spec/attachments/just_headers.csv +1 -0
  81. data/spec/attachments/mostly_empty.csv +2 -0
  82. data/spec/attachments/mostly_empty_copy.xls +0 -0
  83. data/spec/controllers/importable_attachments/attachments_controller_spec.rb +236 -0
  84. data/spec/controllers/importable_attachments/versions_controller_spec.rb +158 -0
  85. data/spec/dummy/README.rdoc +261 -0
  86. data/spec/dummy/Rakefile +7 -0
  87. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  88. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  89. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  90. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  91. data/spec/dummy/app/mailers/.gitkeep +0 -0
  92. data/spec/dummy/app/models/.gitkeep +0 -0
  93. data/spec/dummy/app/models/book.rb +13 -0
  94. data/spec/dummy/app/models/library.rb +54 -0
  95. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  96. data/spec/dummy/config/application.rb +65 -0
  97. data/spec/dummy/config/boot.rb +10 -0
  98. data/spec/dummy/config/database.yml +25 -0
  99. data/spec/dummy/config/environment.rb +5 -0
  100. data/spec/dummy/config/environments/development.rb +37 -0
  101. data/spec/dummy/config/environments/production.rb +67 -0
  102. data/spec/dummy/config/environments/test.rb +37 -0
  103. data/spec/dummy/config/initializers/0_configuration.rb +7 -0
  104. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  105. data/spec/dummy/config/initializers/inflections.rb +15 -0
  106. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  107. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  108. data/spec/dummy/config/initializers/session_store.rb +8 -0
  109. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  110. data/spec/dummy/config/locales/en.yml +5 -0
  111. data/spec/dummy/config/routes.rb +4 -0
  112. data/spec/dummy/config.ru +4 -0
  113. data/spec/dummy/db/migrate/101_create_libraries.rb +10 -0
  114. data/spec/dummy/db/migrate/102_create_books.rb +12 -0
  115. data/spec/dummy/db/schema.rb +57 -0
  116. data/spec/dummy/features/person_uploads_generic_file.feature +11 -0
  117. data/spec/dummy/features/step_definitions/person_uploads_generic_file_steps.rb +11 -0
  118. data/spec/dummy/features/step_definitions/web_steps.rb +211 -0
  119. data/spec/dummy/features/support/capybara.rb +6 -0
  120. data/spec/dummy/features/support/database_cleaner.rb +26 -0
  121. data/spec/dummy/features/support/developer_helpers.rb +47 -0
  122. data/spec/dummy/features/support/env.rb +53 -0
  123. data/spec/dummy/features/support/paths.rb +33 -0
  124. data/spec/dummy/features/support/poltergeist.rb +1 -0
  125. data/spec/dummy/features/support/selectors.rb +39 -0
  126. data/spec/dummy/features/support/transactional_fixtures.rb +14 -0
  127. data/spec/dummy/lib/assets/.gitkeep +0 -0
  128. data/spec/dummy/log/.gitkeep +0 -0
  129. data/spec/dummy/public/404.html +38 -0
  130. data/spec/dummy/public/422.html +38 -0
  131. data/spec/dummy/public/500.html +36 -0
  132. data/spec/dummy/public/favicon.ico +0 -0
  133. data/spec/dummy/script/rails +6 -0
  134. data/spec/dummy/spec/support/.gitkeep +0 -0
  135. data/spec/dummy/spec/support/paperclip.rb +1 -0
  136. data/spec/models/importable_attachments/attachment_spec.rb +177 -0
  137. data/spec/models/importable_attachments/library_spec.rb +155 -0
  138. data/spec/models/importable_attachments/version_spec.rb +25 -0
  139. data/spec/routing/importable_attachments/versions_routing_spec.rb +43 -0
  140. data/spec/spec.opts +5 -0
  141. data/spec/spec_helper.rb +30 -0
  142. metadata +737 -0
@@ -0,0 +1,208 @@
1
+ module ImportableAttachments
2
+ module Importers
3
+ module Csv
4
+ attr_accessor :validate_headers, :destructive_import, :validate_on_import
5
+
6
+ # ImportInto suitable attributes translated from a ImportInto::RECORD_HEADERS
7
+ # inversion, based on RECORD_HEADERS
8
+ attr_accessor :converted_headers
9
+
10
+ def initialize(attributes = nil, options = {})
11
+ bootstrap
12
+ super(attributes, options)
13
+ end
14
+
15
+ # :call-seq:
16
+ # bootstrap
17
+ #
18
+ # :validate_headers - ensures :spreadsheet_columns exist within file
19
+ # :validate_on_import - validates :import_into records upon import (much slower)
20
+ # :timestamp_import - sets timestamps of :import_into records upon import (mildly slower)
21
+ # :destructive_import - makes :import_into reflect most recent file contents (slow)
22
+
23
+ def bootstrap
24
+ @import_rows_to_class = association_symbol_for_rows.to_s.classify.constantize
25
+ @validate_headers = true
26
+ @validate_on_import = ::Configuration.for('attachments').validate_on_import
27
+ @destructive_import = true
28
+ @timestamp_import = true
29
+ @converted_headers = set_converted_headers
30
+ end
31
+
32
+ # :call-seq:
33
+ # attachment= params
34
+ #
35
+ # imports an attachment upon assignment if the record is persisted
36
+ # (if not, after_create hook will import)
37
+
38
+ def attachment=(params)
39
+ super params
40
+ import_attachment if persisted? && attachment && attachment.valid?
41
+ end
42
+
43
+ # :call-seq:
44
+ # import_csv
45
+ #
46
+ # imports a comma-separated value file
47
+
48
+ def import_csv
49
+ return unless attachment.present?
50
+ return if validate_headers && !importable_class_headers_ok?
51
+ transaction do
52
+ send(association_symbol_for_rows).destroy_all if destructive_import
53
+ #send import_method, Hash[importable_columns.zip(importable_columns)].symbolize_keys!
54
+ raise ActiveRecord::Rollback unless import_rows Hash[importable_columns.zip(importable_columns)].symbolize_keys!
55
+ end
56
+ end
57
+
58
+ # :call-seq:
59
+ # import_rows *params
60
+ #
61
+ # imports a CSV file into @import_rows_to_class
62
+
63
+ def import_rows(*params)
64
+ sanitize_data!
65
+
66
+ importer_opts = {}
67
+ importer_opts.merge! timestamps: true # adds data to converted_headers and spreadsheet
68
+ importer_opts.merge! validate: validate_on_import
69
+
70
+ # .dup else .import modifies converted_headers and spreadsheet
71
+ if respond_to? :sanitize_data_callback
72
+ headers, sheet = sanitize_data_callback(converted_headers, spreadsheet)
73
+ else
74
+ headers, sheet = converted_headers.dup, spreadsheet.dup
75
+ end
76
+ results = @import_rows_to_class.import headers, sheet, importer_opts
77
+ reload if persisted?
78
+
79
+ if results && !results.try(:failed_instances).try(:empty?)
80
+ opts = {}
81
+ opts.merge! import_errors_valid: false
82
+
83
+ fail_msg = "failed to import #{results.failed_instances.count} record(s)"
84
+ logger.warn "#{@import_rows_to_class.to_s} #{fail_msg}"
85
+
86
+ @row_errors = results.failed_instances.map {|failed_row| "#{failed_row.errors.messages}: #{failed_row.inspect}"}
87
+ return nil
88
+ else
89
+ @row_errors = []
90
+ return results
91
+ end
92
+
93
+ end
94
+
95
+ protected
96
+
97
+ # :call-seq:
98
+ # set_converted_headers
99
+ #
100
+ # into model attributes representing has_many_attachments RECORD_HEADERS
101
+
102
+ def set_converted_headers
103
+ header_conversion_chart = @import_rows_to_class.const_get(:RECORD_HEADERS).invert
104
+ @converted_headers = importable_columns.map { |col| header_conversion_chart[col] }
105
+ end
106
+
107
+ # :call-seq:
108
+ # importable_columns
109
+ #
110
+ # enumeration of spreadsheet columns to import
111
+
112
+ def importable_columns
113
+ @column_names ||= self.class.spreadsheet_columns
114
+ end
115
+
116
+ # :call-seq:
117
+ # association_symbol_for_rows
118
+ #
119
+ # symbol of association representing individual rows of the spreadsheet
120
+
121
+ def association_symbol_for_rows
122
+ @importing_reflection ||= self.class.import_into
123
+ end
124
+
125
+ # :call-seq:
126
+ # import_method
127
+ #
128
+ # TODO: WRITE ME
129
+
130
+ def import_method
131
+ @import_method ||= self.class.import_method
132
+ end
133
+
134
+ # :call-seq:
135
+ # read_spreadsheet
136
+ #
137
+ # the "raw" file as processed by CSV
138
+
139
+ def read_spreadsheet
140
+ csv_klass = (defined? FasterCSV) ? FasterCSV : CSV
141
+ stream = attachment.io_stream
142
+ if stream.exists?
143
+ csv_klass.read stream.path
144
+ else
145
+ csv_klass.read stream.queued_for_write[:original].path
146
+ end
147
+ end
148
+
149
+ # :call-seq:
150
+ # spreadsheet
151
+ #
152
+ # the rows of the file after the first row (headers)
153
+
154
+ def spreadsheet
155
+ read_spreadsheet[1..-1]
156
+ end
157
+
158
+ # :call-seq:
159
+ # headers
160
+ #
161
+ # headers for the spreadsheet
162
+
163
+ def headers
164
+ read_spreadsheet.first
165
+ end
166
+
167
+ # :call-seq:
168
+ # importable_class_headers_ok?
169
+ #
170
+ # requesting to import headers that are not found in the spreadsheet
171
+
172
+ def importable_class_headers_ok?
173
+ extra_headers = importable_columns.map(&:downcase) - headers
174
+ if extra_headers.empty?
175
+ @columns_not_found = nil
176
+ return true
177
+ else
178
+ @columns_not_found = extra_headers.join(', ')
179
+ return false
180
+ end
181
+ end
182
+
183
+ # :call-seq:
184
+ # sanitize_data!
185
+ #
186
+ # munge data as needed for import e.g. smarter_dates-ish integration
187
+
188
+ def sanitize_data!
189
+ convert_datetimes_intelligently!
190
+ end
191
+
192
+ # :call-seq:
193
+ # convert_datetimes_intelligently!
194
+ #
195
+ # translates English date-ish and-or time-ish language into DateTime instances
196
+
197
+ def convert_datetimes_intelligently!
198
+ dt_attrs = converted_headers.select { |obj| obj.match(/_(?:dt?|at|on)\z/) }
199
+ dt_idxs = dt_attrs.map { |obj| converted_headers.find_index(obj) }
200
+
201
+ spreadsheet.map! { |row|
202
+ dt_idxs.each { |idx| row[idx] = row[idx].try(:to_datetime) || row[idx] }
203
+ row }
204
+ end
205
+
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,37 @@
1
+ module ImportableAttachments
2
+ module Importers
3
+ module Excel
4
+
5
+ require 'iconv'
6
+
7
+ # :call-seq:
8
+ # import_csv
9
+ #
10
+ # imports an Excel (tm) file
11
+
12
+ def import_excel
13
+ column_names = self.class.spreadsheet_columns
14
+ assoc = self.class.import_into
15
+ import_method = self.class.import_method
16
+ return unless attachment.present?
17
+
18
+ stream = attachment.io_stream
19
+ stream_path = if stream.exists?
20
+ stream.path
21
+ else
22
+ stream.queued_for_write[:original].path
23
+ end
24
+ spreadsheet = Roo::Excel.new stream_path
25
+
26
+ spreadsheet.default_sheet = spreadsheet.sheets.first
27
+ headers = (1..column_names.length).map { |n| spreadsheet.cell(1, n).try(:downcase) }
28
+ return unless headers == column_names.map(&:downcase)
29
+ self.send(assoc).destroy_all
30
+ 2.upto(spreadsheet.last_row) do |line|
31
+ self.send(import_method, *(1..column_names.length).map { |n| spreadsheet.cell(line, n) })
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module ImportableAttachments # :nodoc:
2
+ module Importers # :nodoc:
3
+ end
4
+ end
5
+
6
+ require 'importable_attachments/importers/csv'
7
+ require 'importable_attachments/importers/excel'
@@ -0,0 +1,3 @@
1
+ module ImportableAttachments
2
+ VERSION = '0.0.13'
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'configuration'
2
+ require 'importable_attachments/version'
3
+ require 'importable_attachments/engine'
4
+ require 'importable_attachments/base'
5
+ require 'paperclip_processors/save_upload'
6
+ require 'machinist/active_record'
7
+
8
+ module ImportableAttachments
9
+ end
@@ -0,0 +1,33 @@
1
+ require 'paperclip'
2
+
3
+ module Paperclip
4
+
5
+ # force older versions of Paperclip::Upfile to be idempotent
6
+ # https://github.com/thoughtbot/paperclip/issues/315
7
+ class SaveUpload < Processor
8
+ # :call-seq:
9
+ # initialize file, opts, attachment
10
+ #
11
+ # file : File:/tmp/stream_stuff.xls
12
+ # opts : has_attached_file(:processors,:attachment_attr)
13
+ # attachment : Paperclip::Attachment
14
+
15
+ def initialize(file, options = {}, attachment = nil)
16
+ @attachment = attachment
17
+ @file = file
18
+ end
19
+
20
+ # :call-seq:
21
+ # make
22
+ #
23
+ # called by paperclip after_save
24
+
25
+ def make
26
+ @file.read(1)
27
+ @file.rewind
28
+ @file
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :importable_attachments do
3
+ # # Task goes here
4
+ # end
data/script/rails ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/importable_attachments/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
@@ -0,0 +1,6 @@
1
+ title,author,last checkout at
2
+ Great Expectations,Charles Dickens,2013-01-01
3
+ A Wrinkle in Time,Madeleine L'Engle,2013-02-28
4
+ Pride and Prejudice,Jane Austen,2012-04-07
5
+ The Grapes of Wrath,John Steinbeck,2012-12-30
6
+ The Three Musketeers,Alexandre Dumas,2013-03-03
@@ -0,0 +1,6 @@
1
+ title,author,last checkout at
2
+ Great Expectations,Charles Dickens,2013-01-01
3
+ A Wrinkle in Time,Madeleine L'Engle,2013-02-28
4
+ Pride and Prejudice,Jane Austen,2012-04-07
5
+ The Grapes of Wrath,John Steinbeck,2012-12-30
6
+ The Three Musketeers,Alexandre Dumas,2013-03-03
@@ -0,0 +1,4 @@
1
+ title,author,last checkout at
2
+ Jane Eyre,Charlotte Bronte,2013-01-01
3
+ Hamlet,William Shakespeare,2013-02-01
4
+ Les Miserables,Victor Hugo,2013-03-01
File without changes
@@ -0,0 +1,3 @@
1
+ title,author,last checkout at
2
+ The Grapes of Wrath,,2013-01-01
3
+ ,Robert Frost,2013-02-01
@@ -0,0 +1,3 @@
1
+ author,last checkout at
2
+ John Steinbeck,2013-01-01
3
+ Charlotte Bronte,2013-02-01
@@ -0,0 +1 @@
1
+ just,headers,csv,file
@@ -0,0 +1,2 @@
1
+ mostly,empty,csv,file
2
+ just,one,for,development
@@ -0,0 +1,236 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module ImportableAttachments
4
+ describe AttachmentsController do
5
+
6
+ # :call-seq:
7
+ # set_request_environment [:options]
8
+ #
9
+ # sets request headers e.g. X-File-Nmae, content_type, RAW_POST_DATA
10
+
11
+ def set_request_environment(opts = {})
12
+ lopts = request_environment_opts opts
13
+ req_env = request.env
14
+ req_env['X-Requested-With'] = 'XMLHttpRequest' if lopts[:xml]
15
+ req_env['content_type'] = lopts[:content_type]
16
+ req_env['X-File-Name'] = File.basename lopts[:spec_file]
17
+ req_env['RAW_POST_DATA'] = File.new(lopts[:spec_file], 'rb')
18
+ end
19
+
20
+ # :call-seq:
21
+ # request_environment_opts [:options]
22
+ #
23
+ # sets request headers e.g. X-File-Nmae, content_type, RAW_POST_DATA
24
+
25
+ def request_environment_opts(opts = {})
26
+ lopts = {spec_file: @path_to_spec_file, xml: false, content_type: 'application/excel'}
27
+ lopts.merge opts
28
+ end
29
+
30
+ # :call-seq:
31
+ # mock_attachment [:opts]
32
+ #
33
+ # yields a mock ImportableAttachments::Attachment object
34
+
35
+ def mock_attachment(opts = {})
36
+ lopts = {id: 27,
37
+ io_stream: nil,
38
+ io_stream_file_name: 'zero_length.csv',
39
+ io_stream_content_type: 'Test Content Type',
40
+ io_stream_file_size: 1,
41
+ io_stream_updated_at: DateTime.now,
42
+ revision_number: 1,
43
+ attachable_type: nil, attachable_id: nil, version: 1}
44
+ @attachment = mock_model(ImportableAttachments::Attachment, lopts.merge(opts))
45
+ @attachment.stubs(:io_stream).returns(mock_io_stream(attach_to: @attachment))
46
+
47
+ # In the all_controllers_spec case, the file must be copied
48
+ stream_path = @attachment.io_stream.path.to_s
49
+ if @spec_file.path != stream_path
50
+ FileUtils.cp @spec_file, stream_path
51
+ end
52
+
53
+ @attachment
54
+ end
55
+
56
+ # :call-seq:
57
+ # mock_io_stream [:opts]
58
+ #
59
+ # yields a mock Paperclip::Attachment object
60
+
61
+ def mock_io_stream(opts = {})
62
+
63
+ @io_stream = Paperclip::Attachment.new(:io_stream, @attachment,
64
+ {path: ':rails_root/public/:rails_env/:style/:attachable_klass/:id_partition/:basename.:stream_version.:extension',
65
+ preserve_files: true, processors: [:save_upload]})
66
+ spec_dir = File.dirname(@io_stream.path).sub(/(?:\/\.?)?$/, "")
67
+ FileUtils.mkdir_p spec_dir unless File.directory? spec_dir
68
+ @io_stream.stubs(:path).returns(@path_to_spec_file)
69
+ @io_stream
70
+ end
71
+
72
+ before :each do
73
+ @path_to_spec_file = Rails.root.join('spec', 'attachments', 'mostly_empty_copy.xls').to_s
74
+ @spec_file = File.new(@path_to_spec_file, 'rb')
75
+ @spec_file.stubs(:original_filename).returns(File.basename(@path_to_spec_file))
76
+ @uploaded_file = fixture_file_upload @path_to_spec_file, 'application/excel'
77
+
78
+ mock_attachment
79
+ end
80
+
81
+ # TODO: restore @path_to_spec_file via Git... to be anal
82
+ after :each do
83
+ end
84
+
85
+ context 'GET index' do
86
+ it 'assigns all associated attachments as @attachments' do
87
+ attachments = [@attachment]
88
+ ImportableAttachments::Attachment.stubs(:order).with(:io_stream_updated_at).returns(attachments)
89
+ get :index
90
+ assigns(:attachments).should eq(attachments)
91
+ end
92
+ end
93
+
94
+ context 'GET show' do
95
+ it 'should respond with status 200' do
96
+ ImportableAttachments::Attachment.stubs(:find).with(@attachment.id.to_s).returns(@attachment)
97
+ path_to = Rails.root.join('spec', 'attachments', @attachment.io_stream_file_name).to_s
98
+ @io_stream.stubs(:path).returns(path_to)
99
+ #@attachment.stubs(:io_stream).returns(@io_stream)
100
+ get :show, id: @attachment.id.to_s
101
+ response.status.should be 200
102
+ end
103
+
104
+ it 'assigns the requested attachment as @attachment' do
105
+ ImportableAttachments::Attachment.stubs(:find).with(@attachment.id.to_s).returns(@attachment)
106
+ get :show, id: @attachment.id.to_s
107
+ assigns(:attachment).should eq(@attachment)
108
+ end
109
+ end
110
+
111
+ context 'GET download' do
112
+ it 'should send the requested attachment as a file' do
113
+ set_request_environment spec_file: @path_to_spec_file
114
+ ImportableAttachments::Attachment.stubs(:find).with(@attachment.id.to_s).returns(@attachment)
115
+ @attachment.stubs(:io_stream_file_name).returns(File.basename(@path_to_spec_file))
116
+ get :download, id: @attachment.id.to_s
117
+ response.headers['Content-Disposition'].should == "attachment; filename=\"mostly_empty_copy.xls\""
118
+ end
119
+ end
120
+
121
+ context 'GET new' do
122
+ it 'assigns a new attachment as @attachment' do
123
+ get :new
124
+ assigns(:attachment).should be_a_new(ImportableAttachments::Attachment)
125
+ end
126
+ end
127
+
128
+ context 'GET edit' do
129
+ it 'assigns the requested attachment as @attachment' do
130
+ ImportableAttachments::Attachment.stubs(:find).with(@attachment.id.to_s).returns(@attachment)
131
+ get :edit, id: @attachment.id.to_s
132
+ assigns(:attachment).should eq(@attachment)
133
+ end
134
+ end
135
+
136
+ context 'POST create' do
137
+ context 'with valid params' do
138
+ it 'creates a new ImportableAttachments::Attachment' do
139
+ set_request_environment
140
+ expect {
141
+ post :create, attachment: {io_stream: @uploaded_file}
142
+ }.to change(ImportableAttachments::Attachment, :count).by(1)
143
+ end
144
+
145
+ it 'assigns a newly created attachment as @attachment' do
146
+ set_request_environment
147
+ post :create, attachment: {io_stream: @uploaded_file}
148
+ assigns(:attachment).should be_a(ImportableAttachments::Attachment)
149
+ assigns(:attachment).should be_persisted
150
+ end
151
+
152
+ it 'should capture the original filename' do
153
+ set_request_environment
154
+ post :create, attachment: {io_stream: @uploaded_file}
155
+ assigns(:attachment).io_stream_file_name.should == request.env['X-File-Name']
156
+ end
157
+
158
+ it 'should save the file to disk' do
159
+ set_request_environment
160
+ post :create, attachment: {io_stream: @uploaded_file}
161
+ path = assigns(:attachment).io_stream.path
162
+ path.should_not be_blank
163
+ File.exist?(path).should == true
164
+ end
165
+ end
166
+
167
+ context 'with invalid params' do
168
+ it 'assigns a newly created but unsaved attachment as @attachment' do
169
+ set_request_environment
170
+ ImportableAttachments::Attachment.any_instance.expects(:save).returns(false)
171
+ lambda {
172
+ post :create, attachment: {io_stream: nil}
173
+ }.should_not change(@attachment, :io_stream)
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'PUT update' do
179
+ context 'with valid params' do
180
+ before :each do
181
+ @path_to_spec_file = Rails.root.join('spec', 'attachments', 'mostly_empty.csv').to_s
182
+ end
183
+
184
+ it 'updates the requested attachment' do
185
+ set_request_environment
186
+ attachment = ImportableAttachments::Attachment.create! io_stream: fixture_file_upload(@path_to_spec_file, 'text/csv')
187
+ ImportableAttachments::Attachment.stubs(:find).with(attachment.id.to_s).returns(attachment)
188
+ put :update, id: attachment.id.to_s, attachment: {io_stream: @uploaded_file}
189
+ assigns(:attachment).io_stream_file_name.should == @uploaded_file.original_filename
190
+ end
191
+
192
+ it 'assigns the requested attachment as @attachment' do
193
+ set_request_environment
194
+ ImportableAttachments::Attachment.stubs(:find).with(@attachment.id.to_s).returns(@attachment)
195
+ @attachment.stubs(:update_attributes).with('io_stream' => @uploaded_file).returns(@attachment)
196
+ put :update, id: @attachment.id.to_s, attachment: {io_stream: @uploaded_file}
197
+ assigns(:attachment).should eq(@attachment)
198
+ end
199
+
200
+ it 'should create a new version of the file' do
201
+ set_request_environment
202
+ attachment = ImportableAttachments::Attachment.create! io_stream: fixture_file_upload(@path_to_spec_file, 'teext/csv')
203
+ ImportableAttachments::Attachment.stubs(:find).with(attachment.id.to_s).returns(attachment)
204
+ first_path = attachment.io_stream.path
205
+ put :update, id: attachment.id.to_s, attachment: {io_stream: @uploaded_file}
206
+ second_path = assigns(:attachment).io_stream.path
207
+ first_path.should_not == second_path
208
+ end
209
+ end
210
+
211
+ context 'with invalid params' do
212
+ it 'does not update the attachment if the file is not found' do
213
+ set_request_environment
214
+ attachment = ImportableAttachments::Attachment.create! io_stream: @uploaded_file
215
+ ImportableAttachments::Attachment.stubs(:find).with(attachment.id.to_s).returns(attachment)
216
+ attachment.stubs(:update_attributes).with('these' => 'params').returns(false)
217
+ lambda {
218
+ put :update, id: attachment.id.to_s, attachment: {'these' => 'params'}
219
+ }.should_not change(attachment, :io_stream)
220
+ end
221
+ end
222
+ end
223
+
224
+ context 'DELETE destroy' do
225
+ it 'destroys the requested attachment' do
226
+ attachment = ImportableAttachments::Attachment.create! io_stream: @uploaded_file
227
+ ImportableAttachments::Attachment.stubs(:find).with(attachment.id.to_s).returns(attachment)
228
+ expect {
229
+ delete :destroy, id: attachment.id.to_s
230
+ }.to change(ImportableAttachments::Attachment, :count).by(-1)
231
+ end
232
+ end
233
+
234
+ end
235
+ end
236
+