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.
- data/.gitignore +24 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +38 -0
- data/MIT-LICENSE +20 -0
- data/README.md +8 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/assets/images/importable_attachments/.gitkeep +0 -0
- data/app/assets/images/importable_attachments/buttons/.htaccess +5 -0
- data/app/assets/images/importable_attachments/buttons/download_32.png +0 -0
- data/app/assets/images/importable_attachments/buttons/upload_32.png +0 -0
- data/app/assets/javascripts/importable_attachments/application.js +14 -0
- data/app/assets/javascripts/importable_attachments/attachments.coffee +41 -0
- data/app/assets/stylesheets/importable_attachments/application.css +14 -0
- data/app/assets/stylesheets/importable_attachments/attachments.css +4 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/importable_attachments/application_controller.rb +4 -0
- data/app/controllers/importable_attachments/attachments_controller.rb +190 -0
- data/app/controllers/importable_attachments/versions_controller.rb +87 -0
- data/app/helpers/importable_attachments/application_helper.rb +4 -0
- data/app/models/attachment.rb +24 -0
- data/app/models/importable_attachments/attachment.rb +143 -0
- data/app/models/importable_attachments/version.rb +50 -0
- data/app/validators/existing_class_validator.rb +17 -0
- data/app/validators/importable_attachments/csv_validator.rb +36 -0
- data/app/validators/importable_attachments/excel.rb +18 -0
- data/app/validators/importable_attachments/excel_validator.rb +18 -0
- data/app/views/importable_attachments/attachments/_attachment.html.haml +9 -0
- data/app/views/importable_attachments/attachments/_form.html.haml +22 -0
- data/app/views/importable_attachments/attachments/_nested_form.html.haml +20 -0
- data/app/views/importable_attachments/attachments/edit.html.haml +39 -0
- data/app/views/importable_attachments/attachments/index.html.haml +23 -0
- data/app/views/importable_attachments/attachments/index.xml.builder +23 -0
- data/app/views/importable_attachments/attachments/new.html.haml +10 -0
- data/app/views/importable_attachments/attachments/show.html.haml +43 -0
- data/app/views/importable_attachments/versions/_form.html.haml +25 -0
- data/app/views/importable_attachments/versions/edit.html.haml +7 -0
- data/app/views/importable_attachments/versions/index.html.haml +27 -0
- data/app/views/importable_attachments/versions/new.html.haml +5 -0
- data/app/views/importable_attachments/versions/show.html.haml +21 -0
- data/app/views/layouts/_version.html.haml +33 -0
- data/app/views/layouts/importable_attachments/application.html.haml +48 -0
- data/bin/set_lc.sh +47 -0
- data/config/database.yml +25 -0
- data/config/features/attachments.rb +8 -0
- data/config/features/mark_requirements.rb +3 -0
- data/config/features/smarter_dates.rb +3 -0
- data/config/features/versioning.rb +7 -0
- data/config/initializers/0_configuration.rb +7 -0
- data/config/initializers/formtastic.rb +76 -0
- data/config/initializers/generators.rb +10 -0
- data/config/initializers/paperclip.rb +27 -0
- data/config/locales/responders.en.yml +10 -0
- data/config/routes.rb +11 -0
- data/db/migrate/001_create_importable_attachments_versions.rb +14 -0
- data/db/migrate/100_create_attachments.rb +19 -0
- data/importable_attachments.gemspec +81 -0
- data/lib/generators/importable_attachments/install_generator.rb +66 -0
- data/lib/generators/importable_attachments/templates/features/attachments.rb.erb +7 -0
- data/lib/generators/importable_attachments/templates/features/versioning.rb +7 -0
- data/lib/generators/importable_attachments/templates/initializers/paperclip.rb +27 -0
- data/lib/importable_attachments/base.rb +108 -0
- data/lib/importable_attachments/blueprints.rb +9 -0
- data/lib/importable_attachments/engine.rb +8 -0
- data/lib/importable_attachments/importers/csv.rb +208 -0
- data/lib/importable_attachments/importers/excel.rb +37 -0
- data/lib/importable_attachments/importers.rb +7 -0
- data/lib/importable_attachments/version.rb +3 -0
- data/lib/importable_attachments.rb +9 -0
- data/lib/paperclip_processors/save_upload.rb +33 -0
- data/lib/tasks/importable_attachments_tasks.rake +4 -0
- data/script/rails +8 -0
- data/spec/attachments/books.csv +6 -0
- data/spec/attachments/books.txt +6 -0
- data/spec/attachments/books2.csv +4 -0
- data/spec/attachments/empty.csv +0 -0
- data/spec/attachments/failed_instances.csv +3 -0
- data/spec/attachments/invalid_headers.csv +3 -0
- data/spec/attachments/just_headers.csv +1 -0
- data/spec/attachments/mostly_empty.csv +2 -0
- data/spec/attachments/mostly_empty_copy.xls +0 -0
- data/spec/controllers/importable_attachments/attachments_controller_spec.rb +236 -0
- data/spec/controllers/importable_attachments/versions_controller_spec.rb +158 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/book.rb +13 -0
- data/spec/dummy/app/models/library.rb +54 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/0_configuration.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/101_create_libraries.rb +10 -0
- data/spec/dummy/db/migrate/102_create_books.rb +12 -0
- data/spec/dummy/db/schema.rb +57 -0
- data/spec/dummy/features/person_uploads_generic_file.feature +11 -0
- data/spec/dummy/features/step_definitions/person_uploads_generic_file_steps.rb +11 -0
- data/spec/dummy/features/step_definitions/web_steps.rb +211 -0
- data/spec/dummy/features/support/capybara.rb +6 -0
- data/spec/dummy/features/support/database_cleaner.rb +26 -0
- data/spec/dummy/features/support/developer_helpers.rb +47 -0
- data/spec/dummy/features/support/env.rb +53 -0
- data/spec/dummy/features/support/paths.rb +33 -0
- data/spec/dummy/features/support/poltergeist.rb +1 -0
- data/spec/dummy/features/support/selectors.rb +39 -0
- data/spec/dummy/features/support/transactional_fixtures.rb +14 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +38 -0
- data/spec/dummy/public/422.html +38 -0
- data/spec/dummy/public/500.html +36 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/support/.gitkeep +0 -0
- data/spec/dummy/spec/support/paperclip.rb +1 -0
- data/spec/models/importable_attachments/attachment_spec.rb +177 -0
- data/spec/models/importable_attachments/library_spec.rb +155 -0
- data/spec/models/importable_attachments/version_spec.rb +25 -0
- data/spec/routing/importable_attachments/versions_routing_spec.rb +43 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +30 -0
- 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,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
|
+
|
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'
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
just,headers,csv,file
|
|
Binary file
|
|
@@ -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
|
+
|