map_fields 1.0.4 → 2.0.0.beta
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/README.rdoc +23 -30
- data/lib/map_fields/controller.rb +15 -0
- data/lib/map_fields/mapper.rb +108 -0
- data/lib/map_fields/mapping.rb +26 -0
- data/lib/map_fields/params_parser.rb +39 -0
- data/lib/map_fields/railtie.rb +5 -0
- data/lib/map_fields.rb +202 -201
- data/spec/spec_helper.rb +9 -41
- metadata +28 -44
- data/.autotest +0 -2
- data/Rakefile +0 -16
- data/VERSION +0 -1
- data/announcement.txt +0 -12
- data/init.rb +0 -1
- data/spec/application_controller.rb +0 -2
- data/spec/controllers/test_controller_spec.rb +0 -56
- data/spec/lib/params_parser_spec.rb +0 -99
- data/spec/test-file.csv +0 -3
- data/views/map_fields/_map_fields.erb +0 -31
data/README.rdoc
CHANGED
@@ -4,48 +4,48 @@
|
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
7
|
-
A Rails
|
7
|
+
A Rails gem which provides a hook to preview and map the fields of an uploaded CSV file to a pre-defined schema
|
8
8
|
|
9
9
|
== FEATURES/PROBLEMS:
|
10
10
|
|
11
11
|
* Captures the post and provides an intermediate view where a user can map their data to the expected schema
|
12
12
|
* Provides a default mapping view that can be customised
|
13
13
|
* Allows the import to be part of a larger form (The form fields are remembered through the mapping)
|
14
|
+
* Can only handle a single uploaded file at a time
|
14
15
|
|
15
|
-
==
|
16
|
+
== INSTALLATION:
|
16
17
|
|
17
|
-
|
18
|
-
class ListsController < AppliactionController
|
19
|
-
map_fields :create, ['Title', 'First name', 'Last name'], :file_field => :file, :params => [:list]
|
18
|
+
gem install map_fields
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
== EXAMPLE:
|
21
|
+
|
22
|
+
===TestController
|
23
|
+
class TestController < AppliactionController
|
24
24
|
|
25
25
|
def new
|
26
26
|
@list = List.new
|
27
27
|
end
|
28
28
|
|
29
29
|
def create
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@list.contact.create(:title => row[
|
34
|
-
:first_name => row[
|
35
|
-
:last_name => row[
|
30
|
+
map_fields :get_fields, params[:file] do
|
31
|
+
@list = List.create(params[:list])
|
32
|
+
@mapping.rows.each do |row|
|
33
|
+
@list.contact.create(:title => row[:title],
|
34
|
+
:first_name => row[:first_name],
|
35
|
+
:last_name => row[:last_name])
|
36
36
|
end
|
37
37
|
flash[:notice] = 'Contact list created'
|
38
38
|
redirect_to :action => :index
|
39
|
-
else
|
40
|
-
render
|
41
39
|
end
|
42
|
-
rescue MapFields::
|
43
|
-
flash[:error] = 'Please try again'
|
44
|
-
redirect_to :action => :new
|
45
|
-
rescue MapFields::MissingFileContentsError
|
40
|
+
rescue MapFields::MissingFileError
|
46
41
|
flash[:error] = 'Please upload a file'
|
47
42
|
redirect_to :action => :new
|
48
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def get_fields
|
47
|
+
['Title', 'First name', 'Last name']
|
48
|
+
end
|
49
49
|
end
|
50
50
|
|
51
51
|
===New view (new.html.erb)
|
@@ -67,19 +67,12 @@ A Rails plugin which provides a hook to preview and map the fields of an uploade
|
|
67
67
|
===Create view (create.html.erb)
|
68
68
|
<h1>Import a new List</h1>
|
69
69
|
<p>Please map the details you're importing</p>
|
70
|
-
=render :partial => 'map_fields/
|
70
|
+
=render :partial => 'map_fields/mapping'
|
71
71
|
|
72
72
|
== REQUIREMENTS:
|
73
73
|
|
74
|
-
*
|
75
|
-
|
76
|
-
== INSTALL:
|
77
|
-
|
78
|
-
sudo gem install map-fields
|
79
|
-
|
80
|
-
or
|
81
|
-
|
82
|
-
./script/plugin install git://github.com/internuity/map-fields.git
|
74
|
+
* Rails 3.1
|
75
|
+
* Ruby 1.9.3 (Not tested on 1.9.1 or 1.9.2
|
83
76
|
|
84
77
|
== LICENSE:
|
85
78
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'map_fields/mapper'
|
2
|
+
|
3
|
+
module MapFields
|
4
|
+
module Controller
|
5
|
+
def map_fields(fields, file, &block)
|
6
|
+
@mapper = Mapper.new(self, fields, file)
|
7
|
+
if @mapper.mapped?
|
8
|
+
block.call
|
9
|
+
else
|
10
|
+
append_view_path File.expand_path('../../../views/', __FILE__)
|
11
|
+
render
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'map_fields/mapping'
|
4
|
+
require 'map_fields/params_parser'
|
5
|
+
|
6
|
+
module MapFields
|
7
|
+
class MissingFileError < StandardError; end
|
8
|
+
|
9
|
+
class Mapper
|
10
|
+
def initialize(controller, fields, file)
|
11
|
+
params = controller.params
|
12
|
+
@fields = get_fields(controller, fields)
|
13
|
+
@params = ParamsParser.parse(params)
|
14
|
+
|
15
|
+
if file
|
16
|
+
file = save_file controller, file
|
17
|
+
@rows = parse_first_few_lines file
|
18
|
+
else
|
19
|
+
raise MissingFileError unless controller.session[:map_fields_file] && File.exist?(controller.session[:map_fields_file])
|
20
|
+
@mapped = true
|
21
|
+
@rows = map_fields(controller, params.delete(:mapped_fields), fields)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
attr_reader :rows, :fields, :params
|
25
|
+
|
26
|
+
def mapped?
|
27
|
+
@mapped
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def parse_params(params)
|
32
|
+
params = params.except(:controller, :action)
|
33
|
+
end
|
34
|
+
|
35
|
+
def map_fields(controller, mapped_fields, fields)
|
36
|
+
ignore_first_row = mapped_fields.delete(:ignore_first_row)
|
37
|
+
mapping = Mapping.new(mapped_fields, fields)
|
38
|
+
CSVReader.new(controller.session[:map_fields_file], mapping, ignore_first_row)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_fields(controller, fields)
|
42
|
+
if fields.is_a?(Symbol)
|
43
|
+
controller.send(fields)
|
44
|
+
elsif fields.respond_to?(:call)
|
45
|
+
fields.call
|
46
|
+
else
|
47
|
+
fields
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def save_file(controller, file)
|
52
|
+
Tempfile.open(['map_fields', '.csv']) do |tmpfile|
|
53
|
+
tmpfile.write file.read
|
54
|
+
controller.session[:map_fields_file] = tmpfile.path
|
55
|
+
tmpfile.path
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_first_few_lines(file)
|
60
|
+
rows = []
|
61
|
+
rowcount = 0
|
62
|
+
CSV.foreach(file) do |row|
|
63
|
+
rows << row
|
64
|
+
break if (rowcount += 1) >= 10
|
65
|
+
end
|
66
|
+
rows
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class CSVReader
|
71
|
+
def initialize(file, mapping, ignore_first_row)
|
72
|
+
@mapping = mapping
|
73
|
+
@ignore_first_row = ignore_first_row
|
74
|
+
@csv = CSV.open(file, :headers => ignore_first_row)
|
75
|
+
@rows = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def [](index)
|
79
|
+
get_row index
|
80
|
+
end
|
81
|
+
|
82
|
+
def each(&block)
|
83
|
+
@csv.each do |row|
|
84
|
+
block.call CSVRow.new(row, @mapping)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def get_row(index)
|
90
|
+
CSVRow.new((@rows ||= parse_csv)[index], @mapping)
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_csv
|
94
|
+
@csv.read
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class CSVRow
|
99
|
+
def initialize(row, mapping)
|
100
|
+
@row = row
|
101
|
+
@mapping = mapping
|
102
|
+
end
|
103
|
+
|
104
|
+
def [](index)
|
105
|
+
@row[@mapping[index]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MapFields
|
2
|
+
class Mapping
|
3
|
+
def initialize(mapping, fields)
|
4
|
+
@mapping = mapping
|
5
|
+
@fields = fields
|
6
|
+
|
7
|
+
@mapping = mapping.each_with_object({}){ |arr, hash|
|
8
|
+
key = arr[0].to_i
|
9
|
+
value = arr[1].to_i
|
10
|
+
|
11
|
+
hash[key] = value
|
12
|
+
hash[@fields[key]] = value
|
13
|
+
hash[field_to_symbol(@fields[key])] = value
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](index)
|
18
|
+
@mapping[index]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def field_to_symbol(field)
|
23
|
+
field.to_s.downcase.gsub(/[^a-z0-9]+/, '_').to_sym
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MapFields
|
2
|
+
class ParamsParser
|
3
|
+
def self.parse(params, field = nil)
|
4
|
+
result = []
|
5
|
+
params.each do |key, value|
|
6
|
+
next if [:controller, :action].include?(key.to_sym)
|
7
|
+
if field.nil? || field.to_s == key.to_s
|
8
|
+
check_values(value) do |k,v|
|
9
|
+
result << ["#{key.to_s}#{k}", v]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def self.check_values(value, &block)
|
18
|
+
result = []
|
19
|
+
if value.kind_of?(Hash)
|
20
|
+
value.each do |k,v|
|
21
|
+
check_values(v) do |k2,v2|
|
22
|
+
result << ["[#{k.to_s}]#{k2}", v2]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
elsif value.kind_of?(Array)
|
26
|
+
value.each do |v|
|
27
|
+
check_values(v) do |k2, v2|
|
28
|
+
result << ["[]#{k2}", v2]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
result << ["", value] unless value.respond_to?(:read)
|
33
|
+
end
|
34
|
+
result.each do |arr|
|
35
|
+
yield arr[0], arr[1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/map_fields.rb
CHANGED
@@ -1,201 +1,202 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
end
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
1
|
+
require 'map_fields/controller'
|
2
|
+
require 'map_fields/railtie' if defined?(Rails)
|
3
|
+
|
4
|
+
# module MapFields
|
5
|
+
# VERSION = '1.0.0'
|
6
|
+
|
7
|
+
# def self.included(base)
|
8
|
+
# base.extend(ClassMethods)
|
9
|
+
# end
|
10
|
+
|
11
|
+
# def map_fields
|
12
|
+
# default_options = {
|
13
|
+
# :file_field => 'file',
|
14
|
+
# :params => []
|
15
|
+
# }
|
16
|
+
# options = default_options.merge(
|
17
|
+
# self.class.read_inheritable_attribute(:map_fields_options)
|
18
|
+
# )
|
19
|
+
|
20
|
+
# RAILS_DEFAULT_LOGGER.debug("session[:map_fields]: #{session[:map_fields]}")
|
21
|
+
# RAILS_DEFAULT_LOGGER.debug("params[options[:file_field]]: #{params[options[:file_field]]}")
|
22
|
+
# if session[:map_fields].nil? || !params[options[:file_field]].blank?
|
23
|
+
# session[:map_fields] = {}
|
24
|
+
# if params[options[:file_field]].blank?
|
25
|
+
# @map_fields_error = MissingFileContentsError
|
26
|
+
# return
|
27
|
+
# end
|
28
|
+
|
29
|
+
# file_field = params[options[:file_field]]
|
30
|
+
|
31
|
+
# temp_path = File.join(Dir::tmpdir, "map_fields_#{Time.now.to_i}_#{$$}")
|
32
|
+
# File.open(temp_path, 'wb') do |f|
|
33
|
+
# f.write file_field.read
|
34
|
+
# end
|
35
|
+
|
36
|
+
# session[:map_fields][:file] = temp_path
|
37
|
+
# else
|
38
|
+
# if session[:map_fields][:file].nil? || params[:fields].nil?
|
39
|
+
# session[:map_fields] = nil
|
40
|
+
# @map_fields_error = InconsistentStateError
|
41
|
+
# else
|
42
|
+
# expected_fields = self.class.read_inheritable_attribute(:map_fields_fields)
|
43
|
+
# if expected_fields.respond_to?(:call)
|
44
|
+
# expected_fields = expected_fields.call(params)
|
45
|
+
# end
|
46
|
+
# @mapped_fields = MappedFields.new(session[:map_fields][:file],
|
47
|
+
# expected_fields,
|
48
|
+
# params[:fields],
|
49
|
+
# params[:ignore_first_row])
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
# unless @map_fields_error
|
54
|
+
# @rows = []
|
55
|
+
# begin
|
56
|
+
# FasterCSV.foreach(session[:map_fields][:file]) do |row|
|
57
|
+
# @rows << row
|
58
|
+
# break if @rows.size == 10
|
59
|
+
# end
|
60
|
+
# rescue FasterCSV::MalformedCSVError => e
|
61
|
+
# @map_fields_error = e
|
62
|
+
# end
|
63
|
+
# expected_fields = self.class.read_inheritable_attribute(:map_fields_fields)
|
64
|
+
# if expected_fields.respond_to?(:call)
|
65
|
+
# expected_fields = expected_fields.call(params)
|
66
|
+
# end
|
67
|
+
# @fields = ([nil] + expected_fields).inject([]){ |o, e| o << [e, o.size]}
|
68
|
+
# @parameters = []
|
69
|
+
# options[:params].each do |param|
|
70
|
+
# @parameters += ParamsParser.parse(params, param)
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
|
75
|
+
# def mapped_fields
|
76
|
+
# @mapped_fields
|
77
|
+
# end
|
78
|
+
|
79
|
+
# def fields_mapped?
|
80
|
+
# raise @map_fields_error if @map_fields_error
|
81
|
+
# @mapped_fields
|
82
|
+
# end
|
83
|
+
|
84
|
+
# def map_field_parameters(&block)
|
85
|
+
|
86
|
+
# end
|
87
|
+
|
88
|
+
# def map_fields_cleanup
|
89
|
+
# if @mapped_fields
|
90
|
+
# if session[:map_fields][:file]
|
91
|
+
# File.delete(session[:map_fields][:file])
|
92
|
+
# end
|
93
|
+
# session[:map_fields] = nil
|
94
|
+
# @mapped_fields = nil
|
95
|
+
# @map_fields_error = nil
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
|
99
|
+
# module ClassMethods
|
100
|
+
# def map_fields(actions, fields, options = {})
|
101
|
+
# write_inheritable_attribute(:map_fields_fields, fields)
|
102
|
+
# write_inheritable_attribute(:map_fields_options, options)
|
103
|
+
# before_filter :map_fields, :only => actions
|
104
|
+
# after_filter :map_fields_cleanup, :only => actions
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
|
108
|
+
# class MappedFields
|
109
|
+
# attr_reader :mapping, :ignore_first_row, :file
|
110
|
+
|
111
|
+
# def initialize(file, fields, mapping, ignore_first_row)
|
112
|
+
# @file = file
|
113
|
+
# @fields = fields
|
114
|
+
# @mapping = {}
|
115
|
+
# @ignore_first_row = ignore_first_row == '1'
|
116
|
+
|
117
|
+
# mapping.each do |k,v|
|
118
|
+
# unless v.to_i == 0
|
119
|
+
# #Numeric mapping
|
120
|
+
# @mapping[v.to_i - 1] = k.to_i - 1
|
121
|
+
# #Text mapping
|
122
|
+
# @mapping[fields[v.to_i-1]] = k.to_i - 1
|
123
|
+
# #Symbol mapping
|
124
|
+
# sym_key = fields[v.to_i-1].downcase.
|
125
|
+
# gsub(/[-\s\/]+/, '_').
|
126
|
+
# gsub(/[^a-zA-Z0-9_]+/, '').
|
127
|
+
# to_sym
|
128
|
+
# @mapping[sym_key] = k.to_i - 1
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
|
133
|
+
# def is_mapped?(field)
|
134
|
+
# !@mapping[field].nil?
|
135
|
+
# end
|
136
|
+
|
137
|
+
# def each
|
138
|
+
# row_number = 1
|
139
|
+
# FasterCSV.foreach(@file) do |csv_row|
|
140
|
+
# unless row_number == 1 && @ignore_first_row
|
141
|
+
# row = {}
|
142
|
+
# @mapping.each do |k,v|
|
143
|
+
# row[k] = csv_row[v]
|
144
|
+
# end
|
145
|
+
# row.class.send(:define_method, :number) { row_number }
|
146
|
+
# yield(row)
|
147
|
+
# end
|
148
|
+
# row_number += 1
|
149
|
+
# end
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
|
153
|
+
# class InconsistentStateError < StandardError
|
154
|
+
# end
|
155
|
+
|
156
|
+
# class MissingFileContentsError < StandardError
|
157
|
+
# end
|
158
|
+
|
159
|
+
# class ParamsParser
|
160
|
+
# def self.parse(params, field = nil)
|
161
|
+
# result = []
|
162
|
+
# params.each do |key,value|
|
163
|
+
# if field.nil? || field.to_s == key.to_s
|
164
|
+
# check_values(value) do |k,v|
|
165
|
+
# result << ["#{key.to_s}#{k}", v]
|
166
|
+
# end
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
# result
|
170
|
+
# end
|
171
|
+
|
172
|
+
# private
|
173
|
+
# def self.check_values(value, &block)
|
174
|
+
# result = []
|
175
|
+
# if value.kind_of?(Hash)
|
176
|
+
# value.each do |k,v|
|
177
|
+
# check_values(v) do |k2,v2|
|
178
|
+
# result << ["[#{k.to_s}]#{k2}", v2]
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
# elsif value.kind_of?(Array)
|
182
|
+
# value.each do |v|
|
183
|
+
# check_values(v) do |k2, v2|
|
184
|
+
# result << ["[]#{k2}", v2]
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# else
|
188
|
+
# result << ["", value]
|
189
|
+
# end
|
190
|
+
# result.each do |arr|
|
191
|
+
# yield arr[0], arr[1]
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
|
197
|
+
# if defined?(Rails) and defined?(ActionController)
|
198
|
+
# ActionController::Base.send(:include, MapFields)
|
199
|
+
# ActionController::Base.view_paths.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'views'))
|
200
|
+
# #This is a hack but the above code is not enough when using bundler and Rails 2.3.5
|
201
|
+
# ActionController::Base.view_paths.push "app/views"
|
202
|
+
# end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,45 +1,13 @@
|
|
1
|
-
|
2
|
-
RAILS_ENV = 'test'
|
1
|
+
ENV['RAILS_ENV'] = 'test'
|
3
2
|
|
4
|
-
|
5
|
-
$LOAD_PATH << "spec"
|
6
|
-
$LOAD_PATH << "lib"
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
7
4
|
|
8
|
-
require '
|
9
|
-
|
10
|
-
require 'rails/
|
11
|
-
require '
|
12
|
-
require '
|
13
|
-
require 'spec/rails'
|
14
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'map_fields')
|
5
|
+
require 'bundler'
|
6
|
+
require File.expand_path('../../rails_app/config/environment.rb', __FILE__)
|
7
|
+
require 'rails/test_help'
|
8
|
+
require 'rspec/rails'
|
9
|
+
require 'map_fields'
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
Spec::Runner.configure do |config|
|
19
|
-
#config.use_transactional_fixtures = true
|
20
|
-
end
|
21
|
-
|
22
|
-
class TestController < ApplicationController
|
23
|
-
map_fields :create, ['Title', 'First name', 'Last name'], :params => [:user]
|
24
|
-
|
25
|
-
def new
|
26
|
-
end
|
27
|
-
|
28
|
-
def create
|
29
|
-
if fields_mapped?
|
30
|
-
mapped_fields.each do |row|
|
31
|
-
#deal with the data
|
32
|
-
row[0] #=> will be Title
|
33
|
-
row[1] #=> will be First name
|
34
|
-
row[2] #=> will be Last name
|
35
|
-
end
|
36
|
-
render :text => ''
|
37
|
-
else
|
38
|
-
render 'map_fields/_map_fields'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
ActionController::Routing::Routes.draw do |map|
|
44
|
-
map.resources :test
|
11
|
+
RailsApp::Application.routes.draw do
|
12
|
+
resources :test
|
45
13
|
end
|
metadata
CHANGED
@@ -1,71 +1,55 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: map_fields
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0.beta
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Andrew Timberlake
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
date: 2011-06-13 00:00:00 +02:00
|
14
|
-
default_executable:
|
12
|
+
date: 2011-06-13 00:00:00.000000000 Z
|
15
13
|
dependencies: []
|
16
|
-
|
17
14
|
description:
|
18
15
|
email: andrew@andrewtimberlake.com
|
19
16
|
executables: []
|
20
|
-
|
21
17
|
extensions: []
|
22
|
-
|
23
|
-
extra_rdoc_files:
|
18
|
+
extra_rdoc_files:
|
24
19
|
- README.rdoc
|
25
|
-
files:
|
26
|
-
- .autotest
|
20
|
+
files:
|
27
21
|
- README.rdoc
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
-
|
22
|
+
- lib/map_fields/controller.rb
|
23
|
+
- lib/map_fields/mapper.rb
|
24
|
+
- lib/map_fields/mapping.rb
|
25
|
+
- lib/map_fields/params_parser.rb
|
26
|
+
- lib/map_fields/railtie.rb
|
32
27
|
- lib/map_fields.rb
|
33
|
-
- spec/application_controller.rb
|
34
|
-
- spec/controllers/test_controller_spec.rb
|
35
|
-
- spec/lib/params_parser_spec.rb
|
36
28
|
- spec/spec_helper.rb
|
37
|
-
- spec/test-file.csv
|
38
|
-
- views/map_fields/_map_fields.erb
|
39
|
-
has_rdoc: true
|
40
29
|
homepage: http://github.com/internuity/map-fields
|
41
30
|
licenses: []
|
42
|
-
|
43
31
|
post_install_message:
|
44
32
|
rdoc_options: []
|
45
|
-
|
46
|
-
require_paths:
|
33
|
+
require_paths:
|
47
34
|
- lib
|
48
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
36
|
none: false
|
50
|
-
requirements:
|
51
|
-
- -
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version:
|
54
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
42
|
none: false
|
56
|
-
requirements:
|
57
|
-
- -
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version:
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
60
47
|
requirements: []
|
61
|
-
|
62
48
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.
|
49
|
+
rubygems_version: 1.8.10
|
64
50
|
signing_key:
|
65
51
|
specification_version: 3
|
66
|
-
summary: Rails
|
67
|
-
|
68
|
-
|
69
|
-
- spec/controllers/test_controller_spec.rb
|
70
|
-
- spec/lib/params_parser_spec.rb
|
52
|
+
summary: Rails gem to allow a user to map the fields of a CSV to an expected list
|
53
|
+
of fields
|
54
|
+
test_files:
|
71
55
|
- spec/spec_helper.rb
|
data/.autotest
DELETED
data/Rakefile
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "map_fields"
|
8
|
-
gem.summary = %Q{Rails plugin to allow a user to map the fields of a CSV to an expected list of fields}
|
9
|
-
gem.email = "andrew@andrewtimberlake.com"
|
10
|
-
gem.homepage = "http://github.com/internuity/map-fields"
|
11
|
-
gem.authors = ["Andrew Timberlake"]
|
12
|
-
end
|
13
|
-
Jeweler::GemcutterTasks.new
|
14
|
-
rescue LoadError
|
15
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
16
|
-
end
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.0.4
|
data/announcement.txt
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
map-fields version 1.0.0
|
2
|
-
by Andrew Timberlake
|
3
|
-
http://github.com/internuity/map-fields
|
4
|
-
|
5
|
-
== DESCRIPTION
|
6
|
-
|
7
|
-
A Rails plugin which provides a hook to preview and map the fields of an uploaded CSV file to a pre-defined schema
|
8
|
-
|
9
|
-
== CHANGES
|
10
|
-
|
11
|
-
* Initial release
|
12
|
-
|
data/init.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'map_fields'
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe TestController do
|
4
|
-
|
5
|
-
context "on upload" do
|
6
|
-
context "with an attached file" do
|
7
|
-
before do
|
8
|
-
test_file = ActionController::TestUploadedFile.new(File.join(File.dirname(__FILE__), '..', 'test-file.csv'))
|
9
|
-
|
10
|
-
File.should_receive(:open) do |path, flags|
|
11
|
-
path.should match(Regexp.new(Dir::tmpdir))
|
12
|
-
flags.should == 'wb'
|
13
|
-
end
|
14
|
-
FasterCSV.should_receive(:foreach)
|
15
|
-
|
16
|
-
post :create, :file => test_file, :user => {:first_name => 'Test', :last_name => 'User'}
|
17
|
-
end
|
18
|
-
|
19
|
-
it "should assign to @fields" do
|
20
|
-
assigns[:fields].should_not be_blank
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should store the file location in the session" do
|
24
|
-
session[:map_fields][:file].should_not be_blank
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should assign to @parameters" do
|
28
|
-
assigns[:parameters].should_not be_blank
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should have the first name parameter" do
|
32
|
-
assigns[:parameters].should include(['user[first_name]', 'Test'])
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should have the last name parameters" do
|
36
|
-
assigns[:parameters].should include(['user[last_name]', 'User'])
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "without an attached file" do
|
41
|
-
it "should raise an error" do
|
42
|
-
lambda { post :create }.should raise_error
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context "on second post" do
|
48
|
-
it "should map the fields" do
|
49
|
-
session[:map_fields] = {:file => '/tmp/test'}
|
50
|
-
FasterCSV.expects(:foreach)
|
51
|
-
File.expects(:delete).with('/tmp/test')
|
52
|
-
|
53
|
-
post :create, :fields => {"1" => "2", "2" => "3", "3" => "1"}
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe MapFields::ParamsParser do
|
4
|
-
context "with no specific field specified" do
|
5
|
-
it "should be able to parse params[:id] = '1'" do
|
6
|
-
params = {:id => '1'}
|
7
|
-
|
8
|
-
MapFields::ParamsParser.parse(params).should == [["id", "1"]]
|
9
|
-
end
|
10
|
-
|
11
|
-
it "should be able to parse params[:user][:id] = '1'" do
|
12
|
-
params = {:user => {:id => '1'}}
|
13
|
-
|
14
|
-
MapFields::ParamsParser.parse(params).should == [["user[id]", "1"]]
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should be able to parse params[:user][] = '1'" do
|
18
|
-
params = {:user => ['1']}
|
19
|
-
|
20
|
-
MapFields::ParamsParser.parse(params).should == [["user[]", "1"]]
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should be able to parse params[:user][] = '1' with multiple entries" do
|
24
|
-
params = {:user => ['1', '2', '3']}
|
25
|
-
|
26
|
-
MapFields::ParamsParser.parse(params).size.should == 3
|
27
|
-
MapFields::ParamsParser.parse(params)[0].should == ["user[]", "1"]
|
28
|
-
MapFields::ParamsParser.parse(params)[1].should == ["user[]", "2"]
|
29
|
-
MapFields::ParamsParser.parse(params)[2].should == ["user[]", "3"]
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should be able to parse params[:user][:sub][:id] = '1'" do
|
33
|
-
params = {:user => {:sub => {:id => '1'}}}
|
34
|
-
|
35
|
-
MapFields::ParamsParser.parse(params).should == [["user[sub][id]", "1"]]
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should be able to parse params[:user][:sub][] = '1'" do
|
39
|
-
params = {:user => {:sub => ['1']}}
|
40
|
-
|
41
|
-
MapFields::ParamsParser.parse(params).should == [["user[sub][]", "1"]]
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should be able to parse params[:user][:sub][] = '1' with multiple entries" do
|
45
|
-
params = {:user => {:sub => ['1', '2', '3']}}
|
46
|
-
|
47
|
-
MapFields::ParamsParser.parse(params).size.should == 3
|
48
|
-
MapFields::ParamsParser.parse(params)[0].should == ["user[sub][]", "1"]
|
49
|
-
MapFields::ParamsParser.parse(params)[1].should == ["user[sub][]", "2"]
|
50
|
-
MapFields::ParamsParser.parse(params)[2].should == ["user[sub][]", "3"]
|
51
|
-
end
|
52
|
-
|
53
|
-
it "should be able to parse params[:user][:sub][:sub2][] = '1'" do
|
54
|
-
params = {:user => {:sub => {:sub2 => ['1']}}}
|
55
|
-
|
56
|
-
MapFields::ParamsParser.parse(params).should == [["user[sub][sub2][]", "1"]]
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should be able to parse a complicated collection of parameters" do
|
60
|
-
params = {:user => {:sub => {:sub2 => ['1']}},
|
61
|
-
:test => '1',
|
62
|
-
:other => ['collection', 'of', 'parameters'],
|
63
|
-
:checking => {:if => ['it', 'can'],
|
64
|
-
:handle => '1',
|
65
|
-
:big => {:bunch => {:of => {:rubish => ['thrown', 'at'],
|
66
|
-
:it => 'yes'}}}
|
67
|
-
}
|
68
|
-
}
|
69
|
-
|
70
|
-
MapFields::ParamsParser.parse(params).should include(["user[sub][sub2][]", "1"])
|
71
|
-
MapFields::ParamsParser.parse(params).should include(["test", "1"])
|
72
|
-
MapFields::ParamsParser.parse(params).should include(["other[]", "collection"])
|
73
|
-
MapFields::ParamsParser.parse(params).should include(["other[]", "of"])
|
74
|
-
MapFields::ParamsParser.parse(params).should include(["other[]", "parameters"])
|
75
|
-
MapFields::ParamsParser.parse(params).should include(["checking[if][]", "it"])
|
76
|
-
MapFields::ParamsParser.parse(params).should include(["checking[if][]", "can"])
|
77
|
-
MapFields::ParamsParser.parse(params).should include(["checking[handle]", "1"])
|
78
|
-
MapFields::ParamsParser.parse(params).should include(["checking[big][bunch][of][rubish][]", "thrown"])
|
79
|
-
MapFields::ParamsParser.parse(params).should include(["checking[big][bunch][of][rubish][]", "at"])
|
80
|
-
MapFields::ParamsParser.parse(params).should include(["checking[big][bunch][of][it]", "yes"])
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
context "with a specified parameter" do
|
85
|
-
it "should be able to get only the requested field" do
|
86
|
-
params = {:user => {:sub => {:sub2 => ['1']}},
|
87
|
-
:test => ['another', 'parameter']
|
88
|
-
}
|
89
|
-
|
90
|
-
MapFields::ParamsParser.parse(params, :user).should == [["user[sub][sub2][]", "1"]]
|
91
|
-
end
|
92
|
-
|
93
|
-
it "should be able to parse params[:user][:sub][:sub2][] = '1'" do
|
94
|
-
params = {:user => {:sub => {:sub2 => ['1']}}}
|
95
|
-
|
96
|
-
MapFields::ParamsParser.parse(params, :user).should == [["user[sub][sub2][]", "1"]]
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
data/spec/test-file.csv
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
<% form_tag nil, :id => 'map_fields_form', :method => :post do -%>
|
2
|
-
<% @parameters.each do |arr| -%>
|
3
|
-
<%= hidden_field_tag arr[0], arr[1] %>
|
4
|
-
<% end -%>
|
5
|
-
<div class="map_fields">
|
6
|
-
<table cellspacing="0">
|
7
|
-
<thead>
|
8
|
-
<tr>
|
9
|
-
<% (1..@rows[0].size).each do |c| -%>
|
10
|
-
<th><%= select_tag "fields[#{c}]", options_for_select(@fields), :include_blank => true, :class => 'field_options' %></th>
|
11
|
-
<% end -%>
|
12
|
-
</tr>
|
13
|
-
</thead>
|
14
|
-
<tbody>
|
15
|
-
<% @rows.each do |row| -%>
|
16
|
-
<tr>
|
17
|
-
<% row.each do |column| -%>
|
18
|
-
<td><%= h(column) %></td>
|
19
|
-
<% end -%>
|
20
|
-
</tr>
|
21
|
-
<% end -%>
|
22
|
-
</tbody>
|
23
|
-
</table>
|
24
|
-
</div>
|
25
|
-
<div class="option">
|
26
|
-
<%= check_box_tag 'ignore_first_row', '1', true, :id => 'ignore_first_row_option' %><label for="ignore_first_row_option">Ignore the first row (headings)</label>
|
27
|
-
</div>
|
28
|
-
<div class="action">
|
29
|
-
<%= submit_tag 'Import' %>
|
30
|
-
</div>
|
31
|
-
<% end -%>
|