effective_developer 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|