art_vandelay 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![GitHub Actions
|
3
|
+
Demo](https://github.com/thoughtbot/art_vandelay/actions/workflows/ci.yml/badge.svg)](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: []
|