art_vandelay 0.1.0
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +232 -0
- data/Rakefile +8 -0
- data/app/assets/config/art_vandelay_manifest.js +1 -0
- data/app/assets/stylesheets/art_vandelay/application.css +15 -0
- data/app/controllers/art_vandelay/application_controller.rb +4 -0
- data/app/helpers/art_vandelay/application_helper.rb +4 -0
- data/app/jobs/art_vandelay/application_job.rb +4 -0
- data/app/mailers/art_vandelay/application_mailer.rb +6 -0
- data/app/models/art_vandelay/application_record.rb +5 -0
- data/app/views/layouts/art_vandelay/application.html.erb +15 -0
- data/config/routes.rb +2 -0
- data/lib/art_vandelay/engine.rb +5 -0
- data/lib/art_vandelay/version.rb +3 -0
- data/lib/art_vandelay.rb +204 -0
- data/lib/tasks/art_vandelay_tasks.rake +4 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 26029667adccdd12b5098fb5e370050bb3bf4877dceb712cba6ce4f4032aec3f
|
4
|
+
data.tar.gz: '05569d49fa0ad8da29bb07947e77db6425ba12506699b073c27690a644592b52'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ae5ff614ad52587111bf87f10ea736d1087100ff3c782e25c4a8f8e01732f2829e7cd7640172c3e20756f20d6f9faa733b97f6fa53da39b9f442b0d796cd149
|
7
|
+
data.tar.gz: 66c22f9871d06bc1a1d3f4852ef57d919b78960821eabd1f9c7e2c137954db3aa2e2d2d85d31c87f5038710d6a8bc66b139c5d753bc7df9abd3bf40236249a73
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Steve Polito and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
# 🌐 Art Vandelay
|
2
|
+
[](https://github.com/thoughtbot/art_vandelay/actions/workflows/ci.yml)
|
4
|
+
|
5
|
+
Art Vandelay is an importer/exporter for Rails 6.0 and higher.
|
6
|
+
|
7
|
+
Have you ever been on a project where, out of nowhere, someone asks you to send them a CSV of data? You think to yourself, “Ok, cool. No big deal. Just gimme five minutes”, but then that five minutes turns into a few hours. Art Vandelay can help.
|
8
|
+
|
9
|
+
**At a high level, here’s what Art Vandelay can do:**
|
10
|
+
|
11
|
+
- 🕶 Automatically [filters out sensitive information](#%EF%B8%8F-configuration).
|
12
|
+
- 🔁 Export data [in batches](#exporting-in-batches).
|
13
|
+
- 📧 [Email](#artvandelayexportemail_csv) exported data.
|
14
|
+
- 📥 [Import data](#-importing) from a CSV.
|
15
|
+
|
16
|
+
## ✅ Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem "art_vandelay", git: "https://github.com/thoughtbot/art_vandelay"
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
```bash
|
26
|
+
$ bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
## ⚙️ Configuration
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# config/initializers/art_vandelay.rb
|
33
|
+
ArtVandelay.setup do |config|
|
34
|
+
config.filtered_attributes = [:credit_card, :birthday]
|
35
|
+
config.from_address = "no-reply-export@example.com"
|
36
|
+
config.in_batches_of = 5000
|
37
|
+
end
|
38
|
+
```
|
39
|
+
#### Default Values
|
40
|
+
|
41
|
+
|Attribute|Value|Description|
|
42
|
+
|---------|-----|-----------|
|
43
|
+
|`filtered_attributes`|`[:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn]`|Attributes that will be automatically filtered when exported|
|
44
|
+
|`from_address`|`nil`|The email address used when sending an email of exports|
|
45
|
+
|`in_batches_of`|`10000`|The number of records that will be exported into each CSV|
|
46
|
+
|
47
|
+
## 🧰 Usage
|
48
|
+
|
49
|
+
### 📤 Exporting
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
ArtVandelay::Export.new(records, export_sensitive_data: false, attributes: [], in_batches_of: ArtVandelay.in_batches_of)
|
53
|
+
```
|
54
|
+
|
55
|
+
|Argument|Description|
|
56
|
+
|--------|-----------|
|
57
|
+
|`records`|An [Active Record Relation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html) or an instance of an Active Record. E.g. `User.all`, `User.first`, `User.where(...)`, `User.find_by`|
|
58
|
+
|`export_sensitive_data`|Export sensitive data. Defaults to `false`. Can be configured with `ArtVandelay.filtered_attributes`.|
|
59
|
+
|`attributes`|An array attributes to export. Default to all.|
|
60
|
+
|`in_batches_of`|The number of records that will be exported into each CSV. Defaults to 10,000. Can be configured with `ArtVandelay.in_batches_of`|
|
61
|
+
|
62
|
+
#### ArtVandelay::Export#csv
|
63
|
+
|
64
|
+
Returns an instance of `ArtVandelay::Export::Result`.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
result = ArtVandelay::Export.new(User.all).csv
|
68
|
+
# => #<ArtVandelay::Export::Result>
|
69
|
+
|
70
|
+
csv_exports = result.result.csv_exports
|
71
|
+
# => [#<CSV::Table>, #<CSV::Table>, ...]
|
72
|
+
|
73
|
+
csv = csv_exports.first.to_a
|
74
|
+
# => [["id", "email", "password", "created_at", "updated_at"], ["1", "user@example.com", "[FILTERED]", "2022-10-25 09:20:28 UTC", "2022-10-25 09:20:28 UTC"]]
|
75
|
+
```
|
76
|
+
|
77
|
+
##### Exporting Sensitive Data
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
result = ArtVandelay::Export.new(User.all, export_sensitive_data: true).csv
|
81
|
+
# => #<ArtVandelay::Export::Result>
|
82
|
+
|
83
|
+
password = result.csv_exports.first["password"]
|
84
|
+
# => ["bosco"]
|
85
|
+
```
|
86
|
+
|
87
|
+
##### Exporting Specific Attributes
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
result = ArtVandelay::Export.new(User.all, attributes: [:email]).csv
|
91
|
+
# => #<ArtVandelay::Export::Result>
|
92
|
+
|
93
|
+
csv = result.csv_exports.first.to_a
|
94
|
+
# => [["email"], ["george@vandelay_industries.com"]]
|
95
|
+
```
|
96
|
+
|
97
|
+
##### Exporting in Batches
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
result = ArtVandelay::Export.new(User.all, in_batches_of: 100).csv
|
101
|
+
# => #<ArtVandelay::Export::Result>
|
102
|
+
|
103
|
+
csv_size = result.csv_exports.first.size
|
104
|
+
# => 100
|
105
|
+
```
|
106
|
+
|
107
|
+
#### ArtVandelay::Export#email_csv
|
108
|
+
|
109
|
+
Emails the recipient(s) CSV exports as attachments.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
email_csv(to:, from: ArtVandelay.from_address, subject: "#{model_name} export", body: "#{model_name} export")
|
113
|
+
```
|
114
|
+
|
115
|
+
|Argument|Description|
|
116
|
+
|---------|-----|
|
117
|
+
|`to`|An array of email addresses representing who should receive the email.|
|
118
|
+
|`from`|The email address of the sender.|
|
119
|
+
|`subject`|The email subject. Defaults to the following pattern: "User export"|
|
120
|
+
|`body`|The email body. Defaults to the following pattern: "User export"|
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
ArtVandelay::Export
|
124
|
+
.new(User.where.not(confirmed: nil))
|
125
|
+
.email_csv(
|
126
|
+
to: ["george@vandelay_industries.com", "kel_varnsen@vandelay_industries.com"],
|
127
|
+
from: "noreply@vandelay_industries.com",
|
128
|
+
subject: "List of confirmed users",
|
129
|
+
body: "Here's an export of all confirmed users in our database."
|
130
|
+
)
|
131
|
+
# => ActionMailer::Base#mail: processed outbound mail in...
|
132
|
+
```
|
133
|
+
|
134
|
+
### 📥 Importing
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
ArtVandelay::Import.new(model_name, **options)
|
138
|
+
```
|
139
|
+
|
140
|
+
|Argument|Description|
|
141
|
+
|--------|-----------|
|
142
|
+
|`model_name`|The name of the model being imported. E.g. `:users`, `:user`, `"users"` or `"user"`|
|
143
|
+
|`**options`|A hash of options. Available options are `rollback:`|
|
144
|
+
|
145
|
+
#### Options
|
146
|
+
|
147
|
+
|Option|Description|
|
148
|
+
|------|-----------|
|
149
|
+
|`rollback:`|Whether the import should rollback if any of the records fails to save.|
|
150
|
+
|
151
|
+
#### ArtVandelay::Import#csv
|
152
|
+
|
153
|
+
Imports records from the supplied CSV. Returns an instance of `ArtVandelay::Import::Result`.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
csv_string = CSV.generate do |csv|
|
157
|
+
csv << ["email", "password"]
|
158
|
+
csv << ["george@vandelay_industries.com", "bosco"]
|
159
|
+
csv << ["kel_varnsen@vandelay_industries.com", nil]
|
160
|
+
end
|
161
|
+
|
162
|
+
result = ArtVandelay::Import.new(:users).csv(csv_string)
|
163
|
+
# => #<ArtVandelay::Import::Result>
|
164
|
+
|
165
|
+
result.rows_accepted
|
166
|
+
# => [{:row=>["george@vandelay_industries.com", "bosco"], :id=>1}]
|
167
|
+
|
168
|
+
result.rows_rejected
|
169
|
+
# => [{:row=>["kel_varnsen@vandelay_industries.com", nil], :errors=>{:password=>["can't be blank"]}}]
|
170
|
+
```
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
csv(csv_string, **options)
|
174
|
+
```
|
175
|
+
|
176
|
+
|Argument|Description|
|
177
|
+
|--------|-----------|
|
178
|
+
|`csv_string`|Data in the form of a CSV string.|
|
179
|
+
|`**options`|A hash of options. Available options are `headers:` and `attributes:`|
|
180
|
+
|
181
|
+
#### Options
|
182
|
+
|
183
|
+
|Option|Description|
|
184
|
+
|------|-----------|
|
185
|
+
|`headers:`|The CSV headers. Use when the supplied CSV string does not have headers.|
|
186
|
+
|`attributes:`|The attributes the headers should map to. Useful if the headers do not match the model's attributes.|
|
187
|
+
|
188
|
+
##### Rolling back if a record fails to save
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
csv_string = CSV.generate do |csv|
|
192
|
+
csv << ["email", "password"]
|
193
|
+
csv << ["george@vandelay_industries.com", "bosco"]
|
194
|
+
csv << ["kel_varnsen@vandelay_industries.com", nil]
|
195
|
+
end
|
196
|
+
|
197
|
+
result = ArtVandelay::Import.new(:users, rollback: true).csv(csv_string)
|
198
|
+
# => rollback transaction
|
199
|
+
```
|
200
|
+
|
201
|
+
##### Setting headers
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
csv_string = CSV.generate do |csv|
|
205
|
+
csv << ["george@vandelay_industries.com", "bosco"]
|
206
|
+
end
|
207
|
+
|
208
|
+
result = ArtVandelay::Import.new(:users).csv(csv_string, headers: [:email, :password])
|
209
|
+
# => #<ArtVandelay::Import::Result>
|
210
|
+
```
|
211
|
+
|
212
|
+
##### Mapping custom headers
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
csv_string = CSV.generate do |csv|
|
216
|
+
csv << ["email_address", "passcode"]
|
217
|
+
csv << ["george@vandelay_industries.com", "bosco"]
|
218
|
+
end
|
219
|
+
|
220
|
+
result = ArtVandelay::Import.new(:users).csv(csv_string, attributes: {email_address: :email, passcode: :password})
|
221
|
+
# => #<ArtVandelay::Import::Result>
|
222
|
+
```
|
223
|
+
|
224
|
+
## 🙏 Contributing
|
225
|
+
|
226
|
+
1. Run `./bin/setup`.
|
227
|
+
2. Make your changes.
|
228
|
+
3. Ensure `./bin/ci` passes.
|
229
|
+
4. Create a [pull request](https://github.com/thoughtbot/art_vandelay/compare).
|
230
|
+
|
231
|
+
## 📜 License
|
232
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/art_vandelay .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
data/config/routes.rb
ADDED
data/lib/art_vandelay.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require "art_vandelay/version"
|
2
|
+
require "art_vandelay/engine"
|
3
|
+
require "csv"
|
4
|
+
|
5
|
+
module ArtVandelay
|
6
|
+
mattr_accessor :filtered_attributes, :from_address, :in_batches_of
|
7
|
+
@@filtered_attributes = [:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn]
|
8
|
+
@@in_batches_of = 10000
|
9
|
+
|
10
|
+
def self.setup
|
11
|
+
yield self
|
12
|
+
end
|
13
|
+
|
14
|
+
class Error < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class Export
|
18
|
+
class Result
|
19
|
+
attr_reader :csv_exports
|
20
|
+
|
21
|
+
def initialize(csv_exports)
|
22
|
+
@csv_exports = csv_exports
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO attributes: self.filtered_attributes
|
27
|
+
def initialize(records, export_sensitive_data: false, attributes: [], in_batches_of: ArtVandelay.in_batches_of)
|
28
|
+
@records = records
|
29
|
+
@export_sensitive_data = export_sensitive_data
|
30
|
+
@attributes = attributes
|
31
|
+
@in_batches_of = in_batches_of
|
32
|
+
end
|
33
|
+
|
34
|
+
def csv
|
35
|
+
csv_exports = []
|
36
|
+
|
37
|
+
if records.is_a?(ActiveRecord::Relation)
|
38
|
+
records.in_batches(of: in_batches_of) do |relation|
|
39
|
+
csv_exports << CSV.parse(generate_csv(relation), headers: true)
|
40
|
+
end
|
41
|
+
elsif records.is_a?(ActiveRecord::Base)
|
42
|
+
csv_exports << CSV.parse(generate_csv(records), headers: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
Result.new(csv_exports)
|
46
|
+
end
|
47
|
+
|
48
|
+
def email_csv(to:, from: ArtVandelay.from_address, subject: "#{model_name} export", body: "#{model_name} export")
|
49
|
+
if from.nil?
|
50
|
+
raise ArtVandelay::Error, "missing keyword: :from. Alternatively, set a value on ArtVandelay.from_address"
|
51
|
+
end
|
52
|
+
|
53
|
+
mailer = ActionMailer::Base.mail(to: to, from: from, subject: subject, body: body)
|
54
|
+
csv_exports = csv.csv_exports
|
55
|
+
|
56
|
+
csv_exports.each.with_index(1) do |csv, index|
|
57
|
+
if csv_exports.one?
|
58
|
+
mailer.attachments[file_name] = csv
|
59
|
+
else
|
60
|
+
mailer.attachments[file_name(suffix: "-#{index}")] = csv
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
mailer.deliver
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :records, :export_sensitive_data, :attributes, :in_batches_of
|
70
|
+
|
71
|
+
def file_name(**options)
|
72
|
+
options = options.symbolize_keys
|
73
|
+
suffix = options[:suffix]
|
74
|
+
prefix = model_name.downcase
|
75
|
+
timestamp = Time.current.in_time_zone("UTC").strftime("%Y-%m-%d-%H-%M-%S-UTC")
|
76
|
+
|
77
|
+
"#{prefix}-export-#{timestamp}#{suffix}.csv"
|
78
|
+
end
|
79
|
+
|
80
|
+
def filtered_values(attributes)
|
81
|
+
if export_sensitive_data
|
82
|
+
ActiveSupport::ParameterFilter.new([]).filter(attributes).values
|
83
|
+
else
|
84
|
+
ActiveSupport::ParameterFilter.new(ArtVandelay.filtered_attributes).filter(attributes).values
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def generate_csv(relation)
|
89
|
+
CSV.generate do |csv|
|
90
|
+
csv << header
|
91
|
+
if relation.is_a?(ActiveRecord::Relation)
|
92
|
+
relation.each do |record|
|
93
|
+
csv << row(record.attributes)
|
94
|
+
end
|
95
|
+
elsif relation.is_a?(ActiveRecord::Base)
|
96
|
+
csv << row(records.attributes)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def header
|
102
|
+
if attributes.any?
|
103
|
+
model.attribute_names.select do |column_name|
|
104
|
+
standardized_attributes.include?(column_name)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
model.attribute_names
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def model
|
112
|
+
model_name.constantize
|
113
|
+
end
|
114
|
+
|
115
|
+
def model_name
|
116
|
+
records.model_name.name
|
117
|
+
end
|
118
|
+
|
119
|
+
def row(attributes)
|
120
|
+
if self.attributes.any?
|
121
|
+
filtered_values(attributes.slice(*standardized_attributes))
|
122
|
+
else
|
123
|
+
filtered_values(attributes)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def standardized_attributes
|
128
|
+
attributes.map(&:to_s)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Import
|
133
|
+
class Result
|
134
|
+
attr_reader :rows_accepted, :rows_rejected
|
135
|
+
|
136
|
+
def initialize(rows_accepted:, rows_rejected:)
|
137
|
+
@rows_accepted = rows_accepted
|
138
|
+
@rows_rejected = rows_rejected
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize(model_name, **options)
|
143
|
+
@options = options.symbolize_keys
|
144
|
+
@rollback = options[:rollback]
|
145
|
+
@model_name = model_name
|
146
|
+
end
|
147
|
+
|
148
|
+
def csv(csv_string, **options)
|
149
|
+
options = options.symbolize_keys
|
150
|
+
headers = options[:headers] || true
|
151
|
+
attributes = options[:attributes] || {}
|
152
|
+
rows = build_csv(csv_string, headers)
|
153
|
+
|
154
|
+
if rollback
|
155
|
+
# TODO: It would be nice to still return a result object during a
|
156
|
+
# failure
|
157
|
+
active_record.transaction do
|
158
|
+
parse_rows(rows, attributes, raise_on_error: true)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
parse_rows(rows, attributes)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
attr_reader :model_name, :rollback
|
168
|
+
|
169
|
+
def active_record
|
170
|
+
model_name.to_s.classify.constantize
|
171
|
+
end
|
172
|
+
|
173
|
+
def build_csv(csv_string, headers)
|
174
|
+
CSV.parse(csv_string, headers: headers)
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_params(row, attributes)
|
178
|
+
attributes = attributes.stringify_keys
|
179
|
+
|
180
|
+
row.to_h.stringify_keys.transform_keys do |key|
|
181
|
+
attributes[key] || key
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_rows(rows, attributes, **options)
|
186
|
+
options = options.symbolize_keys
|
187
|
+
raise_on_error = options[:raise_on_error] || false
|
188
|
+
result = Result.new(rows_accepted: [], rows_rejected: [])
|
189
|
+
|
190
|
+
rows.each do |row|
|
191
|
+
params = build_params(row, attributes)
|
192
|
+
record = active_record.new(params)
|
193
|
+
|
194
|
+
if raise_on_error ? record.save! : record.save
|
195
|
+
result.rows_accepted << {row: row.fields, id: record.id}
|
196
|
+
else
|
197
|
+
result.rows_rejected << {row: row.fields, errors: record.errors.messages}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
result
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: art_vandelay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Polito
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Art Vandelay is an importer/exporter for Rails
|
28
|
+
email:
|
29
|
+
- stevepolito@hey.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- app/assets/config/art_vandelay_manifest.js
|
38
|
+
- app/assets/stylesheets/art_vandelay/application.css
|
39
|
+
- app/controllers/art_vandelay/application_controller.rb
|
40
|
+
- app/helpers/art_vandelay/application_helper.rb
|
41
|
+
- app/jobs/art_vandelay/application_job.rb
|
42
|
+
- app/mailers/art_vandelay/application_mailer.rb
|
43
|
+
- app/models/art_vandelay/application_record.rb
|
44
|
+
- app/views/layouts/art_vandelay/application.html.erb
|
45
|
+
- config/routes.rb
|
46
|
+
- lib/art_vandelay.rb
|
47
|
+
- lib/art_vandelay/engine.rb
|
48
|
+
- lib/art_vandelay/version.rb
|
49
|
+
- lib/tasks/art_vandelay_tasks.rake
|
50
|
+
homepage: https://github.com/thoughtbot/art_vandelay
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata:
|
54
|
+
homepage_uri: https://github.com/thoughtbot/art_vandelay
|
55
|
+
source_code_uri: https://github.com/thoughtbot/art_vandelay
|
56
|
+
changelog_uri: https://github.com/thoughtbot/art_vandelay/NEWS.md
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubygems_version: 3.3.3
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: Art Vandelay is an importer/exporter for Rails
|
76
|
+
test_files: []
|