importable_attachments 0.0.13 → 0.0.14
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/lib/importable_attachments/base.rb +7 -26
- data/lib/importable_attachments/importers/{csv.rb → importer.rb} +49 -19
- data/lib/importable_attachments/version.rb +1 -1
- data/spec/attachments/books.xls +0 -0
- data/spec/dummy/app/models/library.rb +0 -14
- data/spec/models/importable_attachments/library_spec.rb +14 -0
- metadata +5 -7
- data/app/validators/importable_attachments/csv_validator.rb +0 -36
- data/app/validators/importable_attachments/excel.rb +0 -18
- data/app/validators/importable_attachments/excel_validator.rb +0 -18
- data/lib/importable_attachments/importers/excel.rb +0 -37
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'importable_attachments/importers/importer'
|
1
2
|
module ImportableAttachments::Base
|
2
3
|
module ClassMethods
|
3
4
|
|
@@ -20,6 +21,7 @@ module ImportableAttachments::Base
|
|
20
21
|
install_importable_attachment_assignment_protection
|
21
22
|
|
22
23
|
include InstanceMethods
|
24
|
+
include ImportableAttachments::Importers::Importer
|
23
25
|
|
24
26
|
# for assigning attachment to new record
|
25
27
|
after_create :import_attachment
|
@@ -52,6 +54,9 @@ module ImportableAttachments::Base
|
|
52
54
|
def install_importable_attachment_validations
|
53
55
|
validates :attachment, :associated => true
|
54
56
|
validate do |record|
|
57
|
+
if @invalid_extension
|
58
|
+
invalid_attachment_error "invalid extension: .#{@invalid_extension}"
|
59
|
+
end
|
55
60
|
if @columns_not_found
|
56
61
|
invalid_attachment_error "column(s) not found: #{@columns_not_found}"
|
57
62
|
end
|
@@ -61,10 +66,6 @@ module ImportableAttachments::Base
|
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
64
|
-
# These go in the class calling has_importable_attachment as they are
|
65
|
-
# dependent on mime-type expectations
|
66
|
-
#validates_with CsvValidator, :if => Proc.new {|model| model.attachment.present?}
|
67
|
-
#validates_with ExcelValidator, :if => Proc.new {|model| model.attachment.present?}
|
68
69
|
end
|
69
70
|
|
70
71
|
def install_importable_attachment_assignment_protection
|
@@ -76,29 +77,9 @@ module ImportableAttachments::Base
|
|
76
77
|
|
77
78
|
module InstanceMethods
|
78
79
|
# : call-seq:
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# imports an attachment of a given mime-type (data-stream to ruby),
|
82
|
-
# calls import_rows with a ruby data-store
|
83
|
-
#
|
84
|
-
# NOTE: this is a stub
|
85
|
-
|
86
|
-
def import_attachment
|
87
|
-
raise RuntimeError, '[importable_attachments] .import_attachment not implemented'
|
88
|
-
end
|
89
|
-
|
90
|
-
# : call-seq:
|
91
|
-
# import_rows params
|
92
|
-
#
|
93
|
-
# imports an attachment contents into :import_into association
|
80
|
+
# invalid_attachment_error msg
|
94
81
|
#
|
95
|
-
#
|
96
|
-
|
97
|
-
def import_rows(*opts)
|
98
|
-
return unless self.attachment
|
99
|
-
logger.debug "[importable_attachments] .import_rows #{opts}"
|
100
|
-
raise RuntimeError, '[importable_attachments] .import_rows not implemented'
|
101
|
-
end
|
82
|
+
# adds errors to base record and attachment
|
102
83
|
|
103
84
|
def invalid_attachment_error(msg)
|
104
85
|
attachment.errors.add(:base, msg)
|
@@ -1,12 +1,17 @@
|
|
1
|
+
require 'roo'
|
2
|
+
|
1
3
|
module ImportableAttachments
|
2
4
|
module Importers
|
3
|
-
module
|
5
|
+
module Importer
|
4
6
|
attr_accessor :validate_headers, :destructive_import, :validate_on_import
|
5
7
|
|
6
8
|
# ImportInto suitable attributes translated from a ImportInto::RECORD_HEADERS
|
7
9
|
# inversion, based on RECORD_HEADERS
|
8
10
|
attr_accessor :converted_headers
|
9
11
|
|
12
|
+
# stores the parsed-file for later processing
|
13
|
+
attr_accessor :attachment_as_ruby
|
14
|
+
|
10
15
|
def initialize(attributes = nil, options = {})
|
11
16
|
bootstrap
|
12
17
|
super(attributes, options)
|
@@ -37,16 +42,18 @@ module ImportableAttachments
|
|
37
42
|
|
38
43
|
def attachment=(params)
|
39
44
|
super params
|
40
|
-
import_attachment if persisted? && attachment
|
45
|
+
import_attachment if persisted? && attachment.try(:valid?)
|
41
46
|
end
|
42
47
|
|
43
|
-
# :call-seq:
|
44
|
-
#
|
48
|
+
# : call-seq:
|
49
|
+
# import_attachment
|
45
50
|
#
|
46
|
-
# imports a
|
51
|
+
# imports an attachment of a given mime-type (data-stream to ruby),
|
52
|
+
# calls import_rows with a ruby data-store
|
47
53
|
|
48
|
-
def
|
54
|
+
def import_attachment
|
49
55
|
return unless attachment.present?
|
56
|
+
return unless read_spreadsheet
|
50
57
|
return if validate_headers && !importable_class_headers_ok?
|
51
58
|
transaction do
|
52
59
|
send(association_symbol_for_rows).destroy_all if destructive_import
|
@@ -69,9 +76,9 @@ module ImportableAttachments
|
|
69
76
|
|
70
77
|
# .dup else .import modifies converted_headers and spreadsheet
|
71
78
|
if respond_to? :sanitize_data_callback
|
72
|
-
headers, sheet = sanitize_data_callback(converted_headers, spreadsheet)
|
79
|
+
headers, sheet = sanitize_data_callback(@converted_headers, spreadsheet)
|
73
80
|
else
|
74
|
-
headers, sheet = converted_headers.dup, spreadsheet.dup
|
81
|
+
headers, sheet = @converted_headers.dup, spreadsheet.dup
|
75
82
|
end
|
76
83
|
results = @import_rows_to_class.import headers, sheet, importer_opts
|
77
84
|
reload if persisted?
|
@@ -134,16 +141,36 @@ module ImportableAttachments
|
|
134
141
|
# :call-seq:
|
135
142
|
# read_spreadsheet
|
136
143
|
#
|
137
|
-
# the
|
144
|
+
# sets @attachment_as_ruby to the raw file as processed by roo if the file can be read
|
138
145
|
|
139
146
|
def read_spreadsheet
|
140
|
-
|
141
|
-
|
142
|
-
if stream.exists?
|
143
|
-
csv_klass.read stream.path
|
147
|
+
if !%w(xls xlsx ods xml csv).member?(stream_extension) # required for roo - it checks file extension
|
148
|
+
@invalid_extension = stream_extension
|
144
149
|
else
|
145
|
-
|
150
|
+
@invalid_extension = nil
|
151
|
+
spreadsheet = Roo::Spreadsheet.open stream_path
|
152
|
+
@attachment_as_ruby = spreadsheet.parse
|
146
153
|
end
|
154
|
+
@attachment_as_ruby
|
155
|
+
end
|
156
|
+
|
157
|
+
# :call-seq:
|
158
|
+
# stream_path
|
159
|
+
#
|
160
|
+
# yields path for a readable file, saved or not
|
161
|
+
|
162
|
+
def stream_path
|
163
|
+
@stream = attachment.io_stream
|
164
|
+
@stream.exists? ? @stream.path : @stream.queued_for_write[:original].path
|
165
|
+
end
|
166
|
+
|
167
|
+
# :call-seq:
|
168
|
+
# stream_extension
|
169
|
+
#
|
170
|
+
# yields extension for file
|
171
|
+
|
172
|
+
def stream_extension
|
173
|
+
stream_path.split('.').last
|
147
174
|
end
|
148
175
|
|
149
176
|
# :call-seq:
|
@@ -152,7 +179,7 @@ module ImportableAttachments
|
|
152
179
|
# the rows of the file after the first row (headers)
|
153
180
|
|
154
181
|
def spreadsheet
|
155
|
-
|
182
|
+
@attachment_as_ruby[1..-1]
|
156
183
|
end
|
157
184
|
|
158
185
|
# :call-seq:
|
@@ -161,7 +188,7 @@ module ImportableAttachments
|
|
161
188
|
# headers for the spreadsheet
|
162
189
|
|
163
190
|
def headers
|
164
|
-
|
191
|
+
@attachment_as_ruby.first
|
165
192
|
end
|
166
193
|
|
167
194
|
# :call-seq:
|
@@ -195,11 +222,14 @@ module ImportableAttachments
|
|
195
222
|
# translates English date-ish and-or time-ish language into DateTime instances
|
196
223
|
|
197
224
|
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) }
|
225
|
+
dt_attrs = @converted_headers.select { |obj| obj.match(/_(?:dt?|at|on)\z/) }
|
226
|
+
dt_idxs = dt_attrs.map { |obj| @converted_headers.find_index(obj) }
|
200
227
|
|
201
228
|
spreadsheet.map! { |row|
|
202
|
-
dt_idxs.each { |idx|
|
229
|
+
dt_idxs.each { |idx|
|
230
|
+
to_convert = row[idx]
|
231
|
+
row[idx] = to_convert.try(:to_datetime) || to_convert
|
232
|
+
}
|
203
233
|
row }
|
204
234
|
end
|
205
235
|
|
Binary file
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'importable_attachments/importers'
|
2
|
-
|
3
1
|
class Library < ActiveRecord::Base
|
4
2
|
include SmarterDates if ::Configuration.for('smarter_dates').enabled
|
5
3
|
|
@@ -13,8 +11,6 @@ class Library < ActiveRecord::Base
|
|
13
11
|
extend ImportableAttachments::Base::ClassMethods
|
14
12
|
has_importable_attachment spreadsheet_columns: RECORD_HEADERS,
|
15
13
|
import_into: :books
|
16
|
-
include ImportableAttachments::Importers::Csv
|
17
|
-
validates_with ImportableAttachments::CsvValidator, if: Proc.new { |model| model.attachment.present? && model.attachment.persisted? }
|
18
14
|
end
|
19
15
|
|
20
16
|
# --------------------------------------------------------------------------
|
@@ -35,16 +31,6 @@ class Library < ActiveRecord::Base
|
|
35
31
|
# --------------------------------------------------------------------------
|
36
32
|
# define: behaviors
|
37
33
|
|
38
|
-
# : call-seq:
|
39
|
-
# import_attachment
|
40
|
-
#
|
41
|
-
# imports an attachment of a given mime-type (data-stream to ruby),
|
42
|
-
# calls import_rows with a ruby data-store
|
43
|
-
|
44
|
-
def import_attachment
|
45
|
-
import_csv
|
46
|
-
end
|
47
|
-
|
48
34
|
protected
|
49
35
|
|
50
36
|
def sanitize_data_callback(headers, sheet) # :nodoc:
|
@@ -151,5 +151,19 @@ describe Library do
|
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
154
|
+
context 'importing excel file' do
|
155
|
+
before :each do
|
156
|
+
@attachment = Attachment.new io_stream: File.new(spec_file('books.xls'), 'rb')
|
157
|
+
end
|
158
|
+
|
159
|
+
subject { Library.create(name: 'XYZ Library', address: '123 Main St.') }
|
160
|
+
|
161
|
+
it 'should import a spreadsheet if it is in the right format' do
|
162
|
+
subject.books.should be_empty
|
163
|
+
lambda {
|
164
|
+
subject.attachment = @attachment
|
165
|
+
}.should change(subject.books, :count).by(5)
|
166
|
+
end
|
167
|
+
end
|
154
168
|
end
|
155
169
|
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: importable_attachments
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.14
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Paul Belt
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: haml-rails
|
@@ -524,9 +524,6 @@ files:
|
|
524
524
|
- app/models/importable_attachments/attachment.rb
|
525
525
|
- app/models/importable_attachments/version.rb
|
526
526
|
- app/validators/existing_class_validator.rb
|
527
|
-
- app/validators/importable_attachments/csv_validator.rb
|
528
|
-
- app/validators/importable_attachments/excel.rb
|
529
|
-
- app/validators/importable_attachments/excel_validator.rb
|
530
527
|
- app/views/importable_attachments/attachments/_attachment.html.haml
|
531
528
|
- app/views/importable_attachments/attachments/_form.html.haml
|
532
529
|
- app/views/importable_attachments/attachments/_nested_form.html.haml
|
@@ -566,14 +563,14 @@ files:
|
|
566
563
|
- lib/importable_attachments/blueprints.rb
|
567
564
|
- lib/importable_attachments/engine.rb
|
568
565
|
- lib/importable_attachments/importers.rb
|
569
|
-
- lib/importable_attachments/importers/
|
570
|
-
- lib/importable_attachments/importers/excel.rb
|
566
|
+
- lib/importable_attachments/importers/importer.rb
|
571
567
|
- lib/importable_attachments/version.rb
|
572
568
|
- lib/paperclip_processors/save_upload.rb
|
573
569
|
- lib/tasks/importable_attachments_tasks.rake
|
574
570
|
- script/rails
|
575
571
|
- spec/attachments/books.csv
|
576
572
|
- spec/attachments/books.txt
|
573
|
+
- spec/attachments/books.xls
|
577
574
|
- spec/attachments/books2.csv
|
578
575
|
- spec/attachments/empty.csv
|
579
576
|
- spec/attachments/failed_instances.csv
|
@@ -669,6 +666,7 @@ summary: upload, save-to-disk, attach-to-model_instance, importing
|
|
669
666
|
test_files:
|
670
667
|
- spec/attachments/books.csv
|
671
668
|
- spec/attachments/books.txt
|
669
|
+
- spec/attachments/books.xls
|
672
670
|
- spec/attachments/books2.csv
|
673
671
|
- spec/attachments/empty.csv
|
674
672
|
- spec/attachments/failed_instances.csv
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'csv'
|
2
|
-
|
3
|
-
# validate attachment is a CSV file
|
4
|
-
module ImportableAttachments # :nodoc:
|
5
|
-
class CsvValidator < ActiveModel::Validator
|
6
|
-
|
7
|
-
# :call-seq:
|
8
|
-
# validate :record
|
9
|
-
#
|
10
|
-
# ensures that the record's attachment file name has a .xls extension
|
11
|
-
|
12
|
-
def validate(record)
|
13
|
-
extension = record.attachment.io_stream_file_name.split('.').last
|
14
|
-
if extension.downcase != 'csv'
|
15
|
-
record.errors.add :attachment, 'invalid attachment'
|
16
|
-
record.attachment.errors.add :base, 'File must be a CSV (.csv) file'
|
17
|
-
end
|
18
|
-
|
19
|
-
if defined? FasterCSV
|
20
|
-
begin
|
21
|
-
FasterCSV.read record.attachment.io_stream
|
22
|
-
rescue FasterCSV::MalformedCSVError => err
|
23
|
-
record.errors.add :attachment, 'invalid attachment'
|
24
|
-
record.attachment.errors.add :base, err.messages.join(', ')
|
25
|
-
end
|
26
|
-
else
|
27
|
-
begin
|
28
|
-
CSV.read record.attachment.io_stream.path
|
29
|
-
rescue CSV::MalformedCSVError => err
|
30
|
-
record.errors.add :attachment, 'invalid attachment'
|
31
|
-
record.attachment.errors.add :base, err.messages.join(', ')
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module ImportableAttachments # :nodoc:
|
2
|
-
# validate attachment is an excel file
|
3
|
-
class ExcelValidator < ActiveModel::Validator
|
4
|
-
|
5
|
-
# :call-seq:
|
6
|
-
# validate :record
|
7
|
-
#
|
8
|
-
# ensures that the record's attachment file name has a .xls extension
|
9
|
-
|
10
|
-
def validate(record)
|
11
|
-
extension = record.attachment.io_stream_file_name.split('.').last
|
12
|
-
if extension != 'xls'
|
13
|
-
record.errors[:attachment] << 'File must be an Excel (.xls) file'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module ImportableAttachments # :nodoc:
|
2
|
-
# validate attachment is an excel file
|
3
|
-
class ExcelValidator < ActiveModel::Validator
|
4
|
-
|
5
|
-
# :call-seq:
|
6
|
-
# validate :record
|
7
|
-
#
|
8
|
-
# ensures that the record's attachment file name has a .xls extension
|
9
|
-
|
10
|
-
def validate(record)
|
11
|
-
extension = record.attachment.io_stream_file_name.split('.').last
|
12
|
-
if extension != 'xls'
|
13
|
-
record.errors[:attachment] << 'File must be an Excel (.xls) file'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
@@ -1,37 +0,0 @@
|
|
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
|