csv_row_model 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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +10 -0
  7. data/.yardopts +10 -0
  8. data/CONTRIBUTING.md +7 -0
  9. data/Gemfile +20 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +369 -0
  12. data/Rakefile +1 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +7 -0
  15. data/csv_row_model.gemspec +23 -0
  16. data/lib/csv_row_model.rb +46 -0
  17. data/lib/csv_row_model/concerns/deep_class_var.rb +86 -0
  18. data/lib/csv_row_model/concerns/inspect.rb +10 -0
  19. data/lib/csv_row_model/engine.rb +5 -0
  20. data/lib/csv_row_model/export.rb +70 -0
  21. data/lib/csv_row_model/export/csv.rb +43 -0
  22. data/lib/csv_row_model/export/single_model.rb +47 -0
  23. data/lib/csv_row_model/import.rb +125 -0
  24. data/lib/csv_row_model/import/attributes.rb +105 -0
  25. data/lib/csv_row_model/import/csv.rb +136 -0
  26. data/lib/csv_row_model/import/csv/row.rb +17 -0
  27. data/lib/csv_row_model/import/file.rb +133 -0
  28. data/lib/csv_row_model/import/file/callbacks.rb +16 -0
  29. data/lib/csv_row_model/import/file/validations.rb +39 -0
  30. data/lib/csv_row_model/import/presenter.rb +160 -0
  31. data/lib/csv_row_model/import/single_model.rb +41 -0
  32. data/lib/csv_row_model/model.rb +68 -0
  33. data/lib/csv_row_model/model/children.rb +79 -0
  34. data/lib/csv_row_model/model/columns.rb +73 -0
  35. data/lib/csv_row_model/model/csv_string_model.rb +16 -0
  36. data/lib/csv_row_model/model/single_model.rb +16 -0
  37. data/lib/csv_row_model/validators/boolean_format.rb +11 -0
  38. data/lib/csv_row_model/validators/date_format.rb +9 -0
  39. data/lib/csv_row_model/validators/default_change.rb +6 -0
  40. data/lib/csv_row_model/validators/float_format.rb +9 -0
  41. data/lib/csv_row_model/validators/integer_format.rb +9 -0
  42. data/lib/csv_row_model/validators/number_validator.rb +16 -0
  43. data/lib/csv_row_model/validators/validate_attributes.rb +27 -0
  44. data/lib/csv_row_model/version.rb +3 -0
  45. metadata +116 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fc063f24b8e2038004c3542d79510f7ace7c0c1e
4
+ data.tar.gz: 109db80efb39dbad55894fdaefe2f0e83c2920c3
5
+ SHA512:
6
+ metadata.gz: fecf7a046226eb066808d1773129f0a5487c658fcc07b9428317a1309b05304349c3ac2fc365fae8ca6753d96665b145d757814d22d9a8f829ec0a5be1f37a7c
7
+ data.tar.gz: 1e5b55b8784e2407d78e89a5f0b9d09fbcec78b80942e961d87c553e251e008ad17c149f9e006b85982f09d43f372cc4498bd73cc2a9f066b88080bcfd1e4452
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1 @@
1
+ csv_row_model
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ script: bundle exec rspec
3
+ rvm:
4
+ - 2.1.0
5
+ - ruby-head
6
+ matrix:
7
+ allow_failures:
8
+ - rvm: ruby-head
9
+ env:
10
+ - CODECLIMATE_REPO_TOKEN=6b3e0fb12a56a7aba4b43ca8662d8a5383c831d98b92fc818ea731f4e70e73cd bundle exec rake
@@ -0,0 +1,10 @@
1
+ --no-cache
2
+ --verbose
3
+ --markup markdown
4
+ --markup-provider redcarpet
5
+
6
+ --plugin activesupport-concern
7
+ --plugin yard-delegate
8
+
9
+ --protected
10
+ --private
@@ -0,0 +1,7 @@
1
+ # Contributing
2
+
3
+ 1. Fork it ( https://github.com/[my-github-username]/csv_row_model/fork )
4
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
5
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
6
+ 4. Push to the branch (`git push origin my-new-feature`)
7
+ 5. Create a new Pull Request
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in csv_row_model.gemspec
4
+ gemspec
5
+
6
+ gem "bundler", "> 1.3"
7
+ gem "rake", "~> 10.0"
8
+
9
+ group :test do
10
+ gem "rspec"
11
+ gem "codeclimate-test-reporter"
12
+ gem 'pry-byebug'
13
+ end
14
+
15
+ group :development do
16
+ gem "yard"
17
+ gem "redcarpet"
18
+ gem "yard-activesupport-concern"
19
+ gem "yard-delegate"
20
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Steve Chung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,369 @@
1
+ # CsvRowModel [![Build Status](https://travis-ci.org/FinalCAD/csv_row_model.svg?branch=master)](https://travis-ci.org/FinalCAD/csv_row_model) [![Code Climate](https://codeclimate.com/github/FinalCAD/csv_row_model/badges/gpa.svg)](https://codeclimate.com/github/FinalCAD/csv_row_model) [![Test Coverage](https://codeclimate.com/github/FinalCAD/csv_row_model/badges/coverage.svg)](https://codeclimate.com/github/FinalCAD/csv_row_model/coverage)
2
+
3
+ Import and export your custom CSVs with a intuitive shared Ruby interface.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'csv_row_model'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install csv_row_model
20
+
21
+ ## RowModel
22
+
23
+ Define your `RowModel`.
24
+
25
+ ```ruby
26
+ class ProjectRowModel
27
+ include CsvRowModel::Model
28
+
29
+ # column indices are tracked with each call
30
+ column :id
31
+ column :name
32
+ column :owner_id, header: 'Project Manager' # optional header String, that allows to modify the header of the colmnun
33
+ end
34
+ ```
35
+
36
+ ## Import
37
+
38
+ Automagically maps each column of a CSV row to an attribute of the `RowModel`.
39
+
40
+ ```ruby
41
+ class ProjectImportRowModel < ProjectRowModel
42
+ include CsvRowModel::Import
43
+
44
+ def name
45
+ # mapped_row is raw
46
+ # the calculated original_attribute[:name] is accessible as well
47
+ mapped_row[:name].upcase
48
+ end
49
+ end
50
+ ```
51
+
52
+ And to import:
53
+
54
+ ```ruby
55
+ import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
56
+ row_model = import_file.next
57
+
58
+ row_model.header # => ["id", "name"]
59
+
60
+ row_model.source_row # => ["1", "Some Project Name"]
61
+ row_model.mapped_row # => { id: "1", name: "Some Project Name" }
62
+
63
+ row_model.id # => 1
64
+ row_model.name # => "SOME PROJECT NAME"
65
+ ```
66
+
67
+ `Import::File` also provides the `RowModel` with the previous `RowModel` instance:
68
+
69
+ ```ruby
70
+ row_model.previous # => <ProjectImportRowModel instance>
71
+ row_model.previous.previous # => nil, save memory by avoiding a linked list
72
+ ```
73
+
74
+ ## Presenter
75
+ For complex rows, you can wrap your `RowModel` with a presenter:
76
+
77
+ ```ruby
78
+ class ProjectImportRowModel < ProjectRowModel
79
+ include CsvRowModel::Import
80
+
81
+ # same as above
82
+
83
+ presenter do
84
+ # define your presenter here
85
+
86
+ # this is shorthand for the psuedo_code:
87
+ # def project
88
+ # return if row_model.id.invalid? || row_model.name.invalid?
89
+ #
90
+ # # turn off memoziation with `memoize: false` option
91
+ # @project ||= __the_code_inside_the_block__
92
+ # end
93
+ #
94
+ # and the psuedo_code:
95
+ # def valid?
96
+ # super # calls ActiveModel::Errors code
97
+ # errors.delete(:project) if row_model.id.invalid? || row_model.name.invalid?
98
+ # errors.empty?
99
+ # end
100
+ attribute :project, dependencies: [:id, :name] do
101
+ project = Project.where(id: row_model.id).first
102
+
103
+ # project not found, invalid.
104
+ return unless project
105
+
106
+ project.name = row_model.name
107
+ project
108
+ end
109
+ end
110
+ end
111
+
112
+ # Importing is the same
113
+ import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
114
+ row_model = import_file.next
115
+ presenter = row_model.presenter
116
+
117
+ presenter.row_model # gets the row model underneath
118
+ import_mapper.project.name == presenter.row_model.name # => "SOME PROJECT NAME"
119
+ ```
120
+
121
+ The presenters are designed for another layer of validation---such as with the database.
122
+
123
+ Also, the `attribute` defines a dynamic `#project` method that:
124
+
125
+ 1. Memoizes by default, turn off with `memoize: false` option
126
+ 2. All errors of `row_model` are propagated to the presenter when calling `presenter.valid?`
127
+ 3. Handles dependencies. When any of the dependencies are `invalid?`:
128
+ - The attribute block is not called and the attribute returns `nil`.
129
+ - `presenter.errors` for dependencies are cleaned. For the example above, if `row_model.id/name` are `invalid?`, then
130
+ the `:project` key is removed from the errors, so: `import_mapper.errors.keys # => [:id, :name]`
131
+
132
+ ## Children
133
+
134
+ Child `RowModel` relationships can also be defined:
135
+
136
+ ```ruby
137
+ class UserImportRowModel
138
+ include CsvRowModel::Model
139
+ include CsvRowModel::Import
140
+
141
+ column :id
142
+ column :name
143
+ column :email
144
+
145
+ # uses ProjectImportRowModel#valid? to detect the child row
146
+ has_many :projects, ProjectImportRowModel
147
+ end
148
+
149
+ import_file = CsvRowModel::Import::File.new(file_path, UserImportRowModel)
150
+ row_model = import_file.next
151
+ row_model.projects # => [<ProjectImportRowModel>, ...]
152
+ ```
153
+
154
+ ## Column Options
155
+ ### Default Attributes
156
+ For `Import`, `default_attributes` are calculated as thus:
157
+ - `format_cell`
158
+ - if `value_from_format_cell.blank?`, `default_lambda.call` or nil
159
+ - otherwise, `parse_lambda.call`
160
+
161
+ #### Format Cell
162
+ Override the `format_cell` method to clean/format every cell:
163
+ ```ruby
164
+ class ProjectImportRowModel < ProjectRowModel
165
+ include CsvRowModel::Import
166
+ class << self
167
+ def format_cell(cell, column_name, column_index)
168
+ cell = cell.strip
169
+ cell.to_i.to_s == cell ? cell.to_i : cell
170
+ end
171
+ end
172
+ end
173
+ ```
174
+
175
+ #### Default
176
+ Called when `format_cell` is `value_from_format_cell.blank?`, it sets the default value of the cell:
177
+ ```ruby
178
+ class ProjectImportRowModel < ProjectRowModel
179
+ include CsvRowModel::Import
180
+
181
+ column :id, default: 1
182
+ column :name, default: -> { get_name }
183
+
184
+ def get_name; "John Doe" end
185
+ end
186
+ row_model = ProjectImportRowModel.new(["", ""])
187
+ row_model.id # => 1
188
+ row_model.name # => "John Doe"
189
+ row_model.default_changes # => { id: ["", 1], name: ["", "John Doe"] }
190
+
191
+ ```
192
+
193
+ `DefaultChangeValidator` is provided to allows to add warnings when defaults or set:
194
+
195
+ ```ruby
196
+ class ProjectImportRowModel
197
+ include CsvRowModel::Model
198
+ include CsvRowModel::Input
199
+
200
+ column :id, default: 1
201
+
202
+ warnings do
203
+ validates :id, default_change: true
204
+ # validates :id, presence: true, works too. See ActiveWarnings gem for more.
205
+ end
206
+ end
207
+
208
+ row_model = ProjectImportRowModel.new([""])
209
+
210
+ row_model.unsafe? # => true
211
+ row_model.has_warnings? # => true, same as `#unsafe?`
212
+ row_model.warnings.full_messages # => ["Id changed by default"]
213
+ ```
214
+
215
+ See [Validations](#validations) for more.
216
+
217
+ #### Type
218
+ Automatic type parsing.
219
+
220
+ ```ruby
221
+ class ProjectImportRowModel < ProjectRowModel
222
+ include CsvRowModel::Import
223
+
224
+ column :id, type: Integer
225
+ column :name, parse: ->(original_string) { parse(original_string) }
226
+
227
+ def parse(original_string)
228
+ "#{id} - #{original_string}"
229
+ end
230
+ end
231
+ ```
232
+
233
+ ## Validations
234
+
235
+ Use [`ActiveModel::Validations`](http://api.rubyonrails.org/classes/ActiveModel/Validations.html)
236
+ on your `RowModel` or `Mapper`.
237
+
238
+ Included is [`ActiveWarnings`](https://github.com/s12chung/active_warnings) on `Model` and `Mapper` for warnings
239
+ (such as setting defaults), but not errors (which by default results in a skip).
240
+
241
+ `RowModel` has two validation layers on the `csv_string_model` (a model of `#mapped_row` with `::format_cell` applied) and itself:
242
+
243
+ ```ruby
244
+ class ProjectRowModel
245
+ include CsvRowModel::Model
246
+ include CsvRowModel::Import
247
+
248
+ column :id, type: Integer
249
+
250
+ # this is applied to the parsed CSV on the model
251
+ validates :id, numericality: { greater_than: 0 }
252
+
253
+ csv_string_model do
254
+ # this is applied before the parsed CSV on csv_string_model
255
+ validates :id, integer_format: true, allow_blank: true
256
+ end
257
+ end
258
+
259
+ # Applied to the String
260
+ ProjectRowModel.new(["not_a_number"])
261
+ row_model.valid? # => false
262
+ row_model.errors.full_messages # => ["Id is not a Integer format"]
263
+
264
+ # Applied to the parsed Integer
265
+ row_model = ProjectRowModel.new(["-1"])
266
+ row_model.valid? # => false
267
+ row_model.errors.full_messages # => ["Id must be greater than 0"]
268
+ ```
269
+
270
+ Notice that there are validators given for different types: `Boolean`, `Date`, `Float`, `Integer`:
271
+
272
+ ```ruby
273
+ class ProjectRowModel
274
+ include CsvRowModel::Model
275
+
276
+ # the :validate_type option does the commented code below.
277
+ column :id, type: Integer, validate_type: true
278
+
279
+ # csv_string_model do
280
+ # validates :id, integer_format: true, allow_blank: true
281
+ # end
282
+ end
283
+ ```
284
+
285
+
286
+ ## Callbacks
287
+ `CsvRowModel::Import::File` can be subclassed to access
288
+ [`ActiveModel::Callbacks`](http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html).
289
+
290
+ You can iterate through a file with the `#each` method, which calls `#next` internally:
291
+
292
+ ```ruby
293
+ CsvRowModel::Import::File.new(file_path, ProjectImportRowModel).each do |project_import_model|
294
+ end
295
+ ```
296
+
297
+ Within `#each`, **Skips** and **Aborts** will be done via the `skip?` or `abort?` method on the row model,
298
+ allowing the following callbacks:
299
+
300
+ * yield - `before`, `around`, or `after` the iteration yield
301
+ * skip - `before`
302
+ * abort - `before`
303
+
304
+ and implement the callbacks:
305
+ ```ruby
306
+ class ImportFile < CsvRowModel::Import::File
307
+ around_yield :logger_track
308
+ before_skip :track_skip
309
+
310
+ def logger_track(&block)
311
+ ...
312
+ end
313
+
314
+ def track_skip
315
+ ...
316
+ end
317
+ end
318
+ ```
319
+
320
+ ### Export RowModel
321
+
322
+ Maps each attribute of the `RowModel` to a column of a CSV row.
323
+
324
+ ```ruby
325
+ class ProjectExportRowModel < ProjectRowModel
326
+ include CsvRowModel::Export
327
+
328
+ # Optionally it's possible to override the attribute method, by default it
329
+ # does source_model.public_send(attribute)
330
+ def name
331
+ "#{source_model.id} - #{source_model.name}"
332
+ end
333
+ end
334
+ ```
335
+
336
+ ### Export SingleModel
337
+
338
+ Maps each attribute of the `RowModel` to a row on the CSV.
339
+
340
+ ```ruby
341
+ class ProjectExportRowModel < ProjectRowModel
342
+ include CsvRowModel::Export
343
+ include CsvRowModel::Export::SingleModel
344
+
345
+
346
+ end
347
+ ```
348
+
349
+ And to export:
350
+
351
+ ```ruby
352
+ export_csv = CsvRowModel::Export::Csv.new(ProjectExportRowModel)
353
+ csv_string = export_csv.generate do |csv|
354
+ csv.append_model(project) #optional you can pass a context
355
+ end
356
+ ```
357
+
358
+ #### Format Header
359
+ Override the `format_header` method to format column header names:
360
+ ```ruby
361
+ class ProjectExportRowModel < ProjectRowModel
362
+ include CsvRowModel::Export
363
+ class << self
364
+ def format_header(column_name)
365
+ column_name.to_s.titleize
366
+ end
367
+ end
368
+ end
369
+ ```