importable_attachments 0.0.13

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.
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
+