effective_developer 0.0.6 → 0.0.7
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 +4 -4
- data/README.md +32 -2
- data/app/models/effective/csv_importer.rb +103 -29
- data/lib/effective_developer/version.rb +1 -1
- data/lib/generators/effective_developer/csv_importer.rb.erb +13 -0
- data/lib/tasks/effective_csv_importer.rake +76 -25
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6afcc37e01e11a752f1fa16ea168e739886571a5
|
4
|
+
data.tar.gz: 1f79a862bbaeaec604384015649860c8fc3ca53d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90ce51e217dab774391c2f2e697951a594383738b61b1c1473291804daadbb9dc88409932925faeb000e8abea7248a5a224e7e0bb8f543cfeea564c9e6be2deb
|
7
|
+
data.tar.gz: ce87d2c69e4844d62226ada1b014698269121d3db47265d0170e202ccba8ca168e2d77b4c18272357b01858bd3bdb773e89a180a9df0ba41f3ccc245b7ae60bd
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ Run the bundle command to install it:
|
|
18
18
|
bundle install
|
19
19
|
```
|
20
20
|
|
21
|
-
To use the included command line scripts in any directory, clone this repo:
|
21
|
+
To use the included command line shell scripts in any directory, clone this repo:
|
22
22
|
|
23
23
|
```console
|
24
24
|
git clone git@github.com:code-and-effect/effective_developer.git
|
@@ -70,7 +70,37 @@ A command line script that calls [BFG Repo-Cleaner](https://rtyley.github.io/bfg
|
|
70
70
|
|
71
71
|
# Rake scripts
|
72
72
|
|
73
|
-
##
|
73
|
+
## csv:export
|
74
|
+
|
75
|
+
Exports all database tables to individual .csv files.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
rake export:csv
|
79
|
+
```
|
80
|
+
|
81
|
+
## csv:import::foos
|
82
|
+
|
83
|
+
Where table is the name of a model. Dynamically created rake task when a `/lib/csv_importers/foos.rb` file is present.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
rake csv:import:foos
|
87
|
+
```
|
88
|
+
|
89
|
+
## csv:import::scaffold
|
90
|
+
|
91
|
+
Scaffolds an `Effective::CSVImporter` file for each .csv file in `/lib/csv_importers/data/*.csv`
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
rake csv:import:scaffold
|
95
|
+
```
|
96
|
+
|
97
|
+
or
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
rake csv:import:scaffold[users]
|
101
|
+
```
|
102
|
+
|
103
|
+
## reset_pk_sequence
|
74
104
|
|
75
105
|
If you ever run into the error `duplicate key violates unique constraint (id) error`, run this script:
|
76
106
|
|
@@ -2,22 +2,33 @@ require 'csv'
|
|
2
2
|
|
3
3
|
module Effective
|
4
4
|
class CSVImporter
|
5
|
-
attr_reader :current_row, :last_row
|
5
|
+
attr_reader :current_row, :last_row, :csv_file
|
6
6
|
|
7
7
|
A=0;B=1;C=2;D=3;E=4;F=5;G=6;H=7;I=8;J=9;K=10;L=11;M=12;N=13;
|
8
8
|
O=14;P=15;Q=16;R=17;S=18;T=19;U=20;V=21;W=22;X=23;Y=24;Z=25;
|
9
9
|
AA=26;AB=27;AC=28;AD=29;AE=30;AF=31;AG=32;AH=33;AI=34;AJ=35;
|
10
10
|
AK=36;AL=37;AM=38;AN=39;AO=40;AP=41;AQ=42;AR=43;AS=44;AT=45;
|
11
11
|
|
12
|
-
def initialize(csv_file,
|
13
|
-
@
|
14
|
-
|
12
|
+
def initialize(csv_file = default_csv_file(), header: true)
|
13
|
+
@has_header_row = header
|
14
|
+
|
15
|
+
@csv_file = csv_file
|
16
|
+
raise "#{@csv_file} does not exist" unless File.exists?(@csv_file)
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
+
def columns
|
20
|
+
raise "Please define a method 'def columns' returning a Hash of {id: A, name: B}"
|
19
21
|
end
|
20
22
|
|
23
|
+
def process_row
|
24
|
+
raise "Please define a method 'def process_row' to process your row"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Override me if you need some before/after hooks
|
28
|
+
def before_import ; end
|
29
|
+
def after_import ; end
|
30
|
+
|
31
|
+
# This runs through each row and calls process_row() on it
|
21
32
|
def import!
|
22
33
|
log "Importing #{csv_file.split('/').last.sub('.csv', '')}"
|
23
34
|
|
@@ -30,37 +41,39 @@ module Effective
|
|
30
41
|
log "Import complete (#{@errors_count} errors in #{@has_header_row ? @current_row_number-1 : @current_row_number} rows)"
|
31
42
|
end
|
32
43
|
|
33
|
-
|
34
|
-
|
44
|
+
# Returns an Array of Arrays, with each row run through normalize
|
45
|
+
def rows
|
46
|
+
@rows ||= [].tap do |rows|
|
47
|
+
CSV.foreach(csv_file, headers: @has_header_row) do |row|
|
48
|
+
rows << columns.map { |column, index| normalize(column, row[index].try(:strip).presence) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
35
52
|
|
36
|
-
|
37
|
-
|
53
|
+
# UserStudentInfosImporter.new().where(id: 3, title: 'thing')
|
54
|
+
# Returns an Array of Hashes, representing any row that matches the selector
|
55
|
+
def where(attributes)
|
56
|
+
raise 'expected a Hash of attributes' unless attributes.kind_of?(Hash)
|
57
|
+
attributes.each { |column, _| raise "unknown column :#{column}" unless columns.key?(column) }
|
38
58
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
rescue => e
|
43
|
-
error(e.message)
|
44
|
-
puts row
|
45
|
-
puts e.backtrace.first(3)
|
59
|
+
rows.map do |row|
|
60
|
+
if attributes.all? { |column, value| row[columns[column]] == value }
|
61
|
+
columns.inject({}) { |retval, (column, index)| retval[column] = row[index]; retval }
|
46
62
|
end
|
47
|
-
|
48
|
-
@current_row_number += 1
|
49
|
-
@last_row = row
|
50
|
-
end
|
63
|
+
end.compact
|
51
64
|
end
|
52
65
|
|
53
|
-
def
|
54
|
-
raise
|
66
|
+
def where!(attributes)
|
67
|
+
where(attributes).presence || raise("csv row with #{attributes} not found")
|
55
68
|
end
|
56
69
|
|
57
|
-
def
|
58
|
-
|
70
|
+
def find(attributes)
|
71
|
+
where(attributes).first
|
59
72
|
end
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
def find!(attributes)
|
75
|
+
find(attributes).presence || raise("csv row with #{attributes} not found")
|
76
|
+
end
|
64
77
|
|
65
78
|
# Normalize the value based on column name
|
66
79
|
def normalize(column, value)
|
@@ -73,11 +86,46 @@ module Effective
|
|
73
86
|
parse_datetime(column, value)
|
74
87
|
elsif column.ends_with?('_on') # Date
|
75
88
|
parse_datetime(column, value).beginning_of_day
|
89
|
+
elsif column.ends_with?('_to_i')
|
90
|
+
value.to_i
|
91
|
+
elsif column.ends_with?('_to_f')
|
92
|
+
value.to_f
|
93
|
+
elsif column.ends_with?('_to_s')
|
94
|
+
value.to_s
|
95
|
+
elsif column.ends_with?('_to_a')
|
96
|
+
if ['[]', '{}'].include?(value)
|
97
|
+
[]
|
98
|
+
elsif value.starts_with?('{') && value.ends_with?('}')
|
99
|
+
YAML::load(value).keys.select { |str| str.to_s.present? }
|
100
|
+
else
|
101
|
+
YAML::load(value).to_a.select { |str| str.to_s.present? }
|
102
|
+
end
|
103
|
+
elsif column == 'id' || column.ends_with?('_id')
|
104
|
+
value.present? ? value.to_i : nil
|
76
105
|
else
|
77
|
-
value.presence
|
106
|
+
value.presence
|
78
107
|
end
|
79
108
|
end
|
80
109
|
|
110
|
+
# Takes an object and loops through all columns assigning the current row values
|
111
|
+
def assign_columns(obj, only: [], except: [])
|
112
|
+
assigns = (
|
113
|
+
if only.present?
|
114
|
+
only = Array(only)
|
115
|
+
columns.keep_if { |key, _| only.include?(key) }
|
116
|
+
elsif except.present?
|
117
|
+
except = Array(except)
|
118
|
+
columns.delete_if { |key, _| except.include?(key) }
|
119
|
+
end
|
120
|
+
)
|
121
|
+
|
122
|
+
(assigns || columns).each do |column, _|
|
123
|
+
obj.send("#{column}=", col(column)) if obj.respond_to?(column)
|
124
|
+
end
|
125
|
+
|
126
|
+
obj
|
127
|
+
end
|
128
|
+
|
81
129
|
def log(message)
|
82
130
|
puts "\n#{message}";
|
83
131
|
end
|
@@ -93,6 +141,26 @@ module Effective
|
|
93
141
|
|
94
142
|
protected
|
95
143
|
|
144
|
+
def with_each_row(&block)
|
145
|
+
@current_row_number = (@has_header_row ? 2 : 1)
|
146
|
+
|
147
|
+
CSV.foreach(csv_file, headers: @has_header_row) do |row|
|
148
|
+
@current_row = row
|
149
|
+
|
150
|
+
begin
|
151
|
+
yield
|
152
|
+
print colorize('.', :green)
|
153
|
+
rescue => e
|
154
|
+
error(e.message)
|
155
|
+
puts row
|
156
|
+
puts e.backtrace.first(3)
|
157
|
+
end
|
158
|
+
|
159
|
+
@current_row_number += 1
|
160
|
+
@last_row = row
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
96
164
|
def col(column)
|
97
165
|
raise "unknown column :#{column} passed to col()" unless columns.key?(column) || column.kind_of?(Integer)
|
98
166
|
|
@@ -147,5 +215,11 @@ module Effective
|
|
147
215
|
end
|
148
216
|
end
|
149
217
|
|
218
|
+
private
|
219
|
+
|
220
|
+
def default_csv_file
|
221
|
+
("lib/csv_importers/data/#{self.class.name.gsub('CsvImporters::', '').underscore.gsub('_importer', '')}.csv")
|
222
|
+
end
|
223
|
+
|
150
224
|
end
|
151
225
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module CsvImporters
|
2
|
+
class <%= klass %>Importer < Effective::CSVImporter
|
3
|
+
def columns
|
4
|
+
{<% columns.each_with_index do |column, index| %>
|
5
|
+
<%= column %>: <%= (letters[index] || index) %><%= ',' unless (index+1) == columns.length %><% end %>
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_row
|
10
|
+
raise 'todo'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# effective_csv_importer 1.0
|
2
2
|
|
3
|
-
# Creates one rake task per importer model, as well as a `rake import:all` task.
|
3
|
+
# Creates one rake task per importer model, as well as a `rake csv:import:all` task.
|
4
4
|
|
5
5
|
# Usage:
|
6
6
|
|
@@ -8,41 +8,92 @@
|
|
8
8
|
# Put your csv data in /lib/csv_importers/data/posts.csv
|
9
9
|
# Both filenames should be pluralized
|
10
10
|
|
11
|
-
# rake import:posts (one task created per model)
|
12
|
-
# rake import:all
|
11
|
+
# rake csv:import:posts (one task created per model)
|
12
|
+
# rake csv:import:all
|
13
|
+
# rake csv:import:scaffold
|
14
|
+
# rake csv:import:scaffold[users]
|
15
|
+
# rake csv:export
|
13
16
|
|
14
|
-
namespace :
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
namespace :csv do
|
18
|
+
namespace :import do
|
19
|
+
# Create a rake task to import each csv file
|
20
|
+
Dir['lib/csv_importers/*.rb'].each do |file|
|
21
|
+
importer = file.sub('lib/csv_importers/', '').sub('_importer.rb', '')
|
22
|
+
csv_file = "lib/csv_importers/data/#{importer}.csv"
|
23
|
+
next unless File.exists?(csv_file)
|
19
24
|
|
20
|
-
|
21
|
-
|
25
|
+
# rake csv:import:foo
|
26
|
+
desc "Import #{importer} from #{csv_file}"
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
task importer => :environment do
|
29
|
+
require "#{Rails.application.root}/#{file}"
|
25
30
|
|
26
|
-
|
27
|
-
|
31
|
+
klass = "CsvImporters::#{importer.classify.pluralize}Importer".safe_constantize
|
32
|
+
raise "unable to constantize CsvImporters::#{importer.classify.pluralize}Importer for #{file}" unless klass
|
28
33
|
|
29
|
-
|
34
|
+
klass.new().import!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# rake csv:import:all
|
39
|
+
desc 'Import all from /lib/csv_importers/*.rb'
|
40
|
+
|
41
|
+
task :all => :environment do
|
42
|
+
Dir['lib/csv_importers/*.rb'].each do |file|
|
43
|
+
importer = file.sub('lib/csv_importers/', '').sub('_importer.rb', '')
|
44
|
+
csv_file = "lib/csv_importers/data/#{importer}.csv"
|
45
|
+
next unless File.exists?(csv_file)
|
46
|
+
|
47
|
+
Rake::Task["csv:import:#{importer}"].invoke
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# rake csv:scaffold
|
52
|
+
# rake csv:scaffold[users]
|
53
|
+
desc 'Scaffold an Effective::CSVImporter for each /lib/csv_importers/data/*.csv file'
|
54
|
+
|
55
|
+
task :scaffold, [:file_name] => :environment do |t, args|
|
56
|
+
args.with_defaults(file_name: 'all')
|
57
|
+
|
58
|
+
require 'csv'
|
59
|
+
|
60
|
+
generator = ERB.new(File.read(File.dirname(__FILE__) + '/../generators/effective_developer/csv_importer.rb.erb'))
|
61
|
+
letters = ('A'..'AT').to_a
|
62
|
+
|
63
|
+
Dir['lib/csv_importers/data/*.csv'].each do |file|
|
64
|
+
csv_file = file.split('/').last.gsub('.csv', '')
|
65
|
+
|
66
|
+
next if (Array(args.file_name) != ['all'] && Array(args.file_name).include?(csv_file) == false)
|
67
|
+
|
68
|
+
klass = csv_file.classify.pluralize
|
69
|
+
columns = CSV.open(file, 'r') { |csv| csv.first }
|
70
|
+
|
71
|
+
File.open("#{Rails.root}/lib/csv_importers/#{csv_file}_importer.rb", 'w') do |file|
|
72
|
+
file.write generator.result(binding)
|
73
|
+
end
|
74
|
+
end
|
30
75
|
end
|
31
76
|
end
|
32
|
-
end
|
33
77
|
|
34
|
-
#
|
35
|
-
|
36
|
-
desc "Import all from /lib/csv_importers/*.rb"
|
78
|
+
# rake csv:export
|
79
|
+
desc 'Export all database tables to /tmp/csv_exports/*.csv'
|
37
80
|
|
38
|
-
task :
|
39
|
-
|
40
|
-
importer = file.sub('lib/csv_importers/', '').sub('_importer.rb', '')
|
41
|
-
csv_file = "lib/csv_importers/data/#{importer}.csv"
|
42
|
-
next unless File.exists?(csv_file)
|
81
|
+
task :export => :environment do
|
82
|
+
require 'csv'
|
43
83
|
|
44
|
-
|
84
|
+
path = Rails.root.to_s + '/tmp/csv_exports/'
|
85
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
86
|
+
|
87
|
+
(ActiveRecord::Base.connection.tables - ['schema_migrations']).each do |table|
|
88
|
+
records = ActiveRecord::Base.connection.exec_query("SELECT * FROM #{table} ORDER BY id")
|
89
|
+
|
90
|
+
CSV.open(path + "#{table}.csv", 'wb') do |csv|
|
91
|
+
csv << records.columns
|
92
|
+
records.rows.each { |row| csv << row }
|
93
|
+
end
|
45
94
|
end
|
95
|
+
|
96
|
+
puts "Successfully csv exported #{ActiveRecord::Base.connection.tables.length} tables to #{path}"
|
46
97
|
end
|
47
98
|
|
48
99
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_developer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- lib/effective_developer.rb
|
41
41
|
- lib/effective_developer/engine.rb
|
42
42
|
- lib/effective_developer/version.rb
|
43
|
+
- lib/generators/effective_developer/csv_importer.rb.erb
|
43
44
|
- lib/generators/effective_developer/install_generator.rb
|
44
45
|
- lib/tasks/effective_csv_importer.rake
|
45
46
|
- lib/tasks/pg_pull.rake
|