speaky_csv 0.0.2 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d017f2f25bab00a7df4f498e877f6ef2ba04a5f9
4
- data.tar.gz: 9fda5afbd14f9f576a146e68508f376e0b6b19c2
3
+ metadata.gz: 7fad11aca11ab34b9b05459bbc6282ec467d060a
4
+ data.tar.gz: 0beefbd6193709798f83e1c083022514840d65f8
5
5
  SHA512:
6
- metadata.gz: 3aaa6af2837e4813370d9f3c3b3c1089921e24f4ab4f8f1a12765cc72b22824654945b55fcb8adabfda66986d51eecc6fc54a70e7bc288bb20e3e5dfcd36ce6a
7
- data.tar.gz: 4618b193794df6419843ac667126d3bdf3649fa39989d97ddbf3795cfa815ea4a99a0775e58300daee52233f867704520865f210f12e05d0523ceaeab9d6fcc9
6
+ metadata.gz: 611f717ba2b8539ec43ca525699d92baef358b5950c11d5c6035adba41b2bcd94d3297a42b7a5fb22b92cfc24993e53921d0d0d5c02658416878ca0bb9b88d74
7
+ data.tar.gz: 3f2dfd7c80f985470eff75bc58745a88b54a43b27f4d214410f870468abc7c908a2c11fc7da88de37ed857452b231462e8b1cf8685de4a6d9f3e28875d269255
data/README.md CHANGED
@@ -1,9 +1,51 @@
1
1
  # Speaky CSV
2
2
 
3
- CSV exporting and importing for ActiveRecord and ActiveModel records.
3
+ CSV imports and exports for ActiveRecord.
4
4
 
5
- Speaky lets the format of csv files to be customized, but it does
6
- require certain conventions to be followed. At a high level, the csv
5
+ ## For example
6
+
7
+ Lets say there exists a User class:
8
+
9
+ # in app/models/user.rb
10
+ class User < ActiveRecord::Base
11
+ ...
12
+ end
13
+
14
+ Speaky can be used to import and export user records. The definition of
15
+ the csv format could look like this:
16
+
17
+ # in app/csv/user_csv.rb
18
+ class UserCsv < SpeakyCsv::Base
19
+ define_csv_fields do |config|
20
+ config.field :id, :email, :roles
21
+ end
22
+ end
23
+
24
+ Now lets import some user records. An import csv will always have an
25
+ initial header row, with each following row representing a user record.
26
+ lets import the following csv file (whitespace for clarity):
27
+
28
+ # my_import.csv
29
+ id, email, roles
30
+ 22, admin@example.test, admin
31
+ , newbie@user.test, user
32
+
33
+ This file can be imported like this:
34
+
35
+ File.open "my_import.csv", "r" do |io|
36
+ importer = UserCsv.new.active_record_importer io, User
37
+ importer.each { |user| user.save }
38
+ end
39
+
40
+ ## Custom CSV formats
41
+
42
+ Speaky
43
+
44
+
45
+ Speaky allows customization of csv files to a degree, but some
46
+ conventions need to be followed.
47
+
48
+ At a high level, the csv
7
49
  ends up looking similar to the way active record data gets serialized
8
50
  into form parameters which will be familiar to many rails developers.
9
51
  The advantage of this approach is that associated records be imported
@@ -46,6 +88,12 @@ Once the format is defined records can be exported like this:
46
88
  $ exporter = UserCsv.new.exporter(User.all)
47
89
  $ File.open('users.csv', 'w') { |io| exporter.each { |row| io.write row } }
48
90
 
91
+ TODO:
92
+
93
+ * describe importing to attribute list
94
+ * describe importing to to active records
95
+ * describe how to transform with an enumerator
96
+
49
97
  ## Recommendations
50
98
 
51
99
  * Add `id` and `_destroy` fields for active record models
@@ -62,6 +110,9 @@ Once the format is defined records can be exported like this:
62
110
  * [x] active record import validations
63
111
  * [ ] `has_one` associations
64
112
  * [ ] required fields (make `lock_version` required for example)
113
+ * [ ] transformations for values via accessors on class
114
+ * [ ] public stable api for csv format definition
115
+ * [ ] assign attrs one at a time so they don't all fail together
65
116
 
66
117
  ## Contributing
67
118
 
@@ -1,6 +1,5 @@
1
1
  require 'csv'
2
2
  require 'active_record'
3
- require 'English'
4
3
 
5
4
  module SpeakyCsv
6
5
  # Imports a csv file as unsaved active record instances
@@ -37,6 +36,19 @@ module SpeakyCsv
37
36
  @log_output.string
38
37
  end
39
38
 
39
+ # Add includes options which will be used when querying records.
40
+ #
41
+ # Useful to avoid N+1 type problems. Configured has_manys are automaticaly
42
+ # included and don't need to be specified here.
43
+ def includes(options)
44
+ @includes = options
45
+ end
46
+
47
+ # Add eager_load options which will be used when querying records.
48
+ def eager_load(options)
49
+ @eager_load = options
50
+ end
51
+
40
52
  private
41
53
 
42
54
  def enumerator
@@ -46,13 +58,14 @@ module SpeakyCsv
46
58
  Enumerator.new do |yielder|
47
59
  @rx.each_slice(QUERY_BATCH_SIZE) do |rows|
48
60
  keys = rows.map { |attrs| attrs[@config.primary_key.to_s] }
49
- records = @klass.includes(@config.has_manys.keys)
50
- .where(@config.primary_key => keys)
51
- .inject({}) { |a, e| a[e.send(@config.primary_key).to_s] = e; a }
61
+ query = @klass.includes(@config.has_manys.keys)
62
+ .where(@config.primary_key => keys)
63
+ query = query.includes(@includes) if @includes
64
+ query = query.eager_load(@eager_load) if @eager_load
52
65
 
53
- rows.each do |attrs|
54
- row_index += 1
66
+ records = query.inject({}) { |a, e| a[e.send(@config.primary_key).to_s] = e; a }
55
67
 
68
+ rows.each do |attrs|
56
69
  record = if attrs[@config.primary_key.to_s].present?
57
70
  records[attrs[@config.primary_key.to_s]]
58
71
  else
@@ -83,13 +96,17 @@ module SpeakyCsv
83
96
  end
84
97
  end
85
98
 
86
- begin
87
- record.attributes = attrs
88
- rescue ActiveRecord::UnknownAttributeError
89
- logger.error "[row #{row_index}] record doesn't respond to some configured fields: #{$ERROR_INFO.message}"
99
+ attrs.each do |attr, value|
100
+ writer_method = "#{attr}="
101
+ if record.respond_to? writer_method
102
+ record.send writer_method, value
103
+ else
104
+ logger.error "[row #{row_index}] record doesn't respond to #{attr.inspect}"
105
+ end
90
106
  end
91
107
 
92
108
  yielder << record
109
+ row_index += 1
93
110
  end
94
111
  end
95
112
  end
@@ -1,3 +1,3 @@
1
1
  module SpeakyCsv
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -137,7 +137,7 @@ id,name,author
137
137
 
138
138
  it 'returns an error' do
139
139
  subject.to_a
140
- expect(subject.log).to match(/\[row 2\]/)
140
+ expect(subject.log).to match(/\[row 1\]/)
141
141
  end
142
142
  end
143
143
 
@@ -145,7 +145,7 @@ id,name,author
145
145
  before do
146
146
  presenter_klass.class_eval do
147
147
  define_csv_fields do |d|
148
- d.field :id, :whats_this
148
+ d.field :id, :name, :whats_this
149
149
  end
150
150
  end
151
151
  end
@@ -154,14 +154,19 @@ id,name,author
154
154
 
155
155
  let(:io) do
156
156
  StringIO.new <<-CSV
157
- id,whats_this
158
- 1,unknown
157
+ id,name,whats_this
158
+ 1,Huge Fiction,unknown
159
159
  CSV
160
160
  end
161
161
 
162
162
  it 'adds an error' do
163
163
  expect(record).to eq book
164
- expect(subject.log).to match(/\[row 2\]/)
164
+ expect(subject.log).to match(/\[row 1\]/)
165
+ end
166
+
167
+ it 'assigns other attributes' do
168
+ expect(record).to eq book
169
+ expect(record.name).to eq 'Huge Fiction'
165
170
  end
166
171
  end
167
172
 
@@ -423,4 +428,22 @@ hihihi
423
428
  end
424
429
  end
425
430
  end
431
+
432
+ context "with includes options" do
433
+ before { subject.includes :publisher }
434
+
435
+ it "loads active records with includes option" do
436
+ # I don't know how to verify this so I'll just make sure it doesnt crash
437
+ expect { subject.to_a }.to_not raise_error
438
+ end
439
+ end
440
+
441
+ context "with includes options" do
442
+ before { subject.eager_load :publisher }
443
+
444
+ it "loads active records with eager_log option" do
445
+ # I don't know how to verify this so I'll just make sure it doesnt crash
446
+ expect { subject.to_a }.to_not raise_error
447
+ end
448
+ end
426
449
  end
@@ -10,8 +10,9 @@ ActiveRecord::Base.establish_connection adapter: 'sqlite3',
10
10
 
11
11
  ActiveRecord::Schema.define do
12
12
  create_table :books do |table|
13
- table.column :name, :string
14
13
  table.column :author, :string
14
+ table.column :name, :string
15
+ table.column :publisher_id, :integer
15
16
  end
16
17
 
17
18
  create_table :reviews do |table|
@@ -19,10 +20,15 @@ ActiveRecord::Schema.define do
19
20
  table.column :tomatoes, :integer
20
21
  table.column :publication, :string
21
22
  end
23
+
24
+ create_table :publishers do |table|
25
+ table.column :name, :string
26
+ end
22
27
  end
23
28
 
24
29
  class Book < ActiveRecord::Base
25
30
  has_many :reviews, inverse_of: :book
31
+ belongs_to :publisher, inverse_of: :books
26
32
  accepts_nested_attributes_for :reviews, allow_destroy: true
27
33
  end
28
34
 
@@ -31,6 +37,10 @@ class Review < ActiveRecord::Base
31
37
  accepts_nested_attributes_for :book, allow_destroy: true
32
38
  end
33
39
 
40
+ class Publisher < ActiveRecord::Base
41
+ has_many :books, inverse_of: :publisher
42
+ end
43
+
34
44
  RSpec.configure do |config|
35
45
  config.before(:suite) do
36
46
  DatabaseCleaner.strategy = :transaction
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: speaky_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Hartford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-23 00:00:00.000000000 Z
11
+ date: 2015-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel