active_record_importer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43724c0cedd35917d14e3439f96482c8f2357943
4
- data.tar.gz: 7db97349cd0eafa87a2296906f56e6543c58395f
3
+ metadata.gz: 07cd0e7debf64daad8259915aa82f993c1c86f1c
4
+ data.tar.gz: 98df0543814ef29f1bbe2ec031c9f02394db723e
5
5
  SHA512:
6
- metadata.gz: 22f7c0e2eb463b49e0a717d1ee4da685c111424681f20cbc2f578ebe5bbbab57277384f05715aca3abf22ffb25fe957efe8820ee916d6add08cc8ba82fa82e84
7
- data.tar.gz: e90f8ab8ce49228e47441a3df8adcd214547fec041583912760e0792b1b6eff631f8378723d9cc73af23fbcc4605b8c58ee59578ca433a1c8a6070f413026502
6
+ metadata.gz: 521db635648cfe9da9d5ce938f58c519db6e6e16c7428df3efa25a251fce72f655c17ee98d61f9d98ac949b377107f07be5377b531e0ac1597edf4e9238aa5b2
7
+ data.tar.gz: 407647a49e620e64cc47c125f106017b132e24c44fb039d4e882f6ceea7f5b3ec9ed90ec65d8ed0e638ef79590c3bf660f50bf8f94a3557f765ed260facd09c6
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # ActiveRecordImporter
2
2
 
3
- Supports only Rails 4... (I'll release soon an update to support Rails 5)
3
+ Supports only Rails 4 and 5
4
4
 
5
5
  This gem helps you insert/update records easily. For now, it only accepts CSV file.
6
6
  This also helps you monitor how many rows are imported, and how many rows failed.
7
+ This gem also allows you to easily import to any model with few configurations.
8
+
7
9
  I'll release an update to enable this on background job.
8
- This gem allows you to easily import to any model with few configurations.
9
10
 
10
11
  ## Installation
11
12
 
@@ -46,7 +47,7 @@ class ActiveRecordImporterMigration < ActiveRecord::Migration
46
47
  end
47
48
  ```
48
49
 
49
- #### Import Model:
50
+ #### Add Import Model:
50
51
  ```ruby
51
52
  class Import < ActiveRecord::Base
52
53
  extend Enumerize
@@ -57,6 +58,7 @@ class Import < ActiveRecord::Base
57
58
  default: :upsert
58
59
 
59
60
  has_attached_file :file
61
+ has_attached_file :failed_file
60
62
 
61
63
  attr_accessor :execute_on_create
62
64
 
@@ -67,15 +69,22 @@ class Import < ActiveRecord::Base
67
69
  content_type: %w(text/plain text/csv)
68
70
  }
69
71
 
70
- after_create :execute, if: :execute_on_create
72
+ validates_attachment :failed_file,
73
+ content_type: {
74
+ content_type: %w(text/plain text/csv)
75
+ }
71
76
 
72
- # I'll add import options in the next release
77
+ # I'll add import options in the next major release
73
78
  # accepts_nested_attributes_for :import_options, allow_destroy: true
74
79
 
75
80
  def execute
76
81
  resource_class.import!(self, execute_on_create)
77
82
  end
78
83
 
84
+ def execute!
85
+ resource_class.import!(self, true)
86
+ end
87
+
79
88
  def resource_class
80
89
  resource.safe_constantize
81
90
  end
@@ -86,11 +95,20 @@ class Import < ActiveRecord::Base
86
95
 
87
96
  ##
88
97
  # Override this if you prefer have
89
- # a private permissions or you have
98
+ # private permissions or you have
90
99
  # private methods for reading files
91
100
  ##
92
101
  def import_file
93
- local_path? ? file.path : file.url
102
+ local_path?(file) ? file.path : file.url
103
+ end
104
+
105
+ ##
106
+ # Override this method if you have
107
+ # private permissions or you have private methods
108
+ # for reading/writing uploaded files
109
+ ##
110
+ def failed_file_path
111
+ local_path?(failed_file) ? failed_file.path : failed_file.url
94
112
  end
95
113
 
96
114
  private
@@ -100,13 +118,14 @@ class Import < ActiveRecord::Base
100
118
  errors.add(:find_options, "can't be blank") if find_options.blank?
101
119
  end
102
120
 
103
- def local_path?
104
- File.exist? import_file.file.path
121
+ def local_path?(f)
122
+ File.exist? f.path
105
123
  end
106
124
  end
107
125
  ```
108
126
 
109
- Add `acts_as_importable` to a model to make it importable
127
+ ### Add `acts_as_importable` to any ActiveRecord model to make it importable
128
+
110
129
  ```ruby
111
130
  class User < ActiveRecord::Base
112
131
  acts_as_importable
@@ -120,9 +139,35 @@ class User < ActiveRecord::Base
120
139
  last_name: 'dela Cruz' },
121
140
  find_options: %i(email),
122
141
  before_save: Proc.new { |user| user.password = 'temporarypassword123' }
142
+ after_save: Proc.new { |user| puts "THIS IS CALLED AFTER OBJECT IS SAVED" }
123
143
  end
124
144
  ```
125
145
 
146
+ If you're using ActiveRecord::Store, you may import values to your accessors by including them in the configuration:
147
+ ```ruby
148
+ class User < ActiveRecord::Base
149
+ store :properties, accessors: [:first_key, :second_key]
150
+
151
+ acts_as_importable store_accessors: [:first_key, :second_key]
152
+ end
153
+ ```
154
+
155
+ ### Add import form
156
+ This is a sample import HAML form:
157
+ ```ruby
158
+ # resource is your Model name
159
+ = f.input :resource
160
+ # batch_size is useful for large csv file
161
+ = f.input :batch_size
162
+ # insert_methods: [:upsert, :insert, :error_on_duplicate]
163
+ = f.input :insert_method, collection: insert_methods, class: 'form-control insert-method'
164
+ # `find_options` are the list of columns you want to use to update a certain instance or
165
+ # error when a duplicate is found. This is not required when your insert_method is `:insert`
166
+ = f.input :find_options
167
+ = f.input :file, as: :file,
168
+ input_html: { accept: '.csv' }
169
+ ```
170
+
126
171
  You may also add some options from the SmarterCSV gem:
127
172
 
128
173
  | Option | Default
@@ -135,24 +180,51 @@ You may also add some options from the SmarterCSV gem:
135
180
  | :chunk_size | 500
136
181
  | :col_sep | ","
137
182
 
138
- #### I'll add more options SOON!
183
+ https://github.com/tilo/smarter_csv
184
+
185
+
186
+ ```ruby
187
+ class User < ActiveRecord::Base
188
+ acts_as_importable csv_opts: {
189
+ chunk_size: 2000,
190
+ col_sep: '|',
191
+ convert_values_to_numeric: { only: [:age, :salary] }
192
+ }
193
+ end
194
+ ```
195
+
196
+ `I'll add more options SOON!`
139
197
 
140
- #### Imports Controller:
198
+
199
+ ### Create Imports Controller:
141
200
  ```ruby
142
201
  class ImportsController < ApplicationController
143
202
 
144
203
  def create
145
204
  @import = Import.create!(import_params)
146
- @import.execute_on_create = true
147
- @import.execute
205
+ @import.execute!
148
206
  end
149
207
 
208
+ private
209
+
210
+ def import_params
211
+ params.require(:import).permit(:file, :resource, :insert_method, :batch_size)
212
+ end
150
213
  end
151
214
  ```
152
215
 
153
- You may include execute_on_create as a checkbox field in your Import form so you don't have to
154
- include `@import.execute_on_create = true` in your controller.
155
-
216
+ #### Run it via Rails Console:
217
+ ```ruby
218
+ File.open(PATH_TO_CSV_FILE) do |file|
219
+ @import = Import.create!(
220
+ resource: 'User',
221
+ file: file,
222
+ insert_method: 'upsert',
223
+ find_options: 'first_name,last_name'
224
+ )
225
+ end
226
+ @import.execute!
227
+ ```
156
228
 
157
229
 
158
230
  ## Development
@@ -169,4 +241,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/michae
169
241
  ## License
170
242
 
171
243
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
172
-
@@ -17,11 +17,15 @@ module ActiveRecordImporter
17
17
  autoload :Importable, 'active_record_importer/importable'
18
18
  autoload :InstanceBuilder, 'active_record_importer/instance_builder'
19
19
  autoload :OptionsBuilder, 'active_record_importer/options_builder'
20
- autoload :AttributesBuilder, 'active_record_importer/attributes_builder'
21
- autoload :FindOptionsBuilder, 'active_record_importer/find_options_builder'
22
20
  autoload :TransitionProcessor, 'active_record_importer/transition_processor'
23
21
  autoload :ImportCallbacker, 'active_record_importer/import_callbacker'
24
- autoload :Helpers, 'active_record_importer/helpers'
22
+ autoload :FailedFileBuilder, 'active_record_importer/failed_file_builder'
23
+
24
+ module Attribute
25
+ autoload :AttributesBuilder, 'active_record_importer/attribute/attributes_builder'
26
+ autoload :FindOptionsBuilder, 'active_record_importer/attribute/find_options_builder'
27
+ autoload :Helpers, 'active_record_importer/attribute/helpers'
28
+ end
25
29
 
26
30
  require 'active_record_importer/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
27
31
  end
@@ -0,0 +1,69 @@
1
+ module ActiveRecordImporter
2
+ module Attribute
3
+ class AttributesBuilder
4
+ include Attribute::Helpers
5
+
6
+ attr_reader :importable, :row_attrs, :processed_attrs
7
+
8
+ delegate :importer_options, to: :importable
9
+ delegate :importable_columns,
10
+ :default_attributes,
11
+ :find_assoc_opts,
12
+ to: :importer_options
13
+
14
+ def initialize(importable, row_attrs)
15
+ @importable = importable
16
+ @row_attrs = row_attrs
17
+ @processed_attrs = {}
18
+ end
19
+
20
+ def build
21
+ force_encode_attributes
22
+ fetch_time_attributes
23
+ processed_attrs
24
+ end
25
+
26
+ private
27
+
28
+ def default_attrs
29
+ def_attrs = { importing: true }
30
+ default_attributes.each do |key, value|
31
+ def_attrs[key] = fetch_value(value)
32
+ end
33
+ end
34
+
35
+ def force_encode_attributes
36
+ @processed_attrs = force_utf8_encode(merged_attributes)
37
+ end
38
+
39
+ def fetch_time_attributes
40
+ @processed_attrs.merge!(time_attributes(processed_attrs))
41
+ end
42
+
43
+ def fetch_value(value)
44
+ case value
45
+ when Proc
46
+ value.call(row_attrs)
47
+ when Symbol
48
+ importable.send(value, row_attrs)
49
+ else
50
+ value
51
+ end
52
+ end
53
+
54
+ def merged_attributes
55
+ attributes = row_attrs.slice(*importable_columns)
56
+ row_attrs = attributes.inject({}) do |row_attrs, key_value|
57
+ row_attrs[key_value.first] = key_value.last if key_value.last.present?
58
+ row_attrs
59
+ end
60
+ default_attrs.merge(row_attrs)
61
+ end
62
+
63
+ def has_column?(column)
64
+ return if column.blank?
65
+ importable_columns.include?(column.to_sym)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ module ActiveRecordImporter
2
+ module Attribute
3
+ class FindOptionsBuilder
4
+ include Virtus.model
5
+ include Helpers
6
+
7
+ attribute :resource, String
8
+ attribute :attrs, Hash, default: {}
9
+ attribute :find_options, String
10
+ attribute :prefix, String
11
+
12
+ def build
13
+ get_find_opts
14
+ slice_attributes
15
+ end
16
+
17
+ private
18
+
19
+ def klass
20
+ resource.safe_constantize
21
+ end
22
+
23
+ delegate :importer_options, to: :klass
24
+
25
+ delegate :required_attributes, to: :importer_options
26
+
27
+ def get_find_opts
28
+ @options = strip_and_symbolize
29
+ @options ||= importer_options.find_options || required_attributes
30
+ @options
31
+ end
32
+
33
+ def slice_attributes
34
+ return attrs.slice(*@options).compact if prefix.blank?
35
+
36
+ @options.inject({}) do |attr, key|
37
+ attr[key] = attrs[prefixed_key(key)].presence
38
+ attr
39
+ end.compact
40
+ end
41
+
42
+ def prefixed_key(key)
43
+ "#{prefix}#{key}".to_sym
44
+ end
45
+
46
+ def strip_and_symbolize
47
+ return if find_options.blank?
48
+ find_options.split(',').map do |key|
49
+ key.strip.to_sym
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveRecordImporter
2
+ module Attribute
3
+ module Helpers
4
+
5
+ def parse_datetime(datetime = nil)
6
+ return if datetime.blank?
7
+ Time.parse(datetime)
8
+ end
9
+
10
+ def force_utf8_encode(data = {})
11
+ return data if data.blank?
12
+
13
+ data.keys.each do |key|
14
+ data[key] = data[key].force_encoding('UTF-8') if data[key].is_a?(String)
15
+ end
16
+
17
+ data
18
+ end
19
+
20
+ def time_attributes(data = {})
21
+ attrs = {}
22
+ attrs[:created_at] = parse_datetime(data[:created_at]) || Time.now
23
+ attrs[:updated_at] = parse_datetime(data[:updated_at]) || attrs[:created_at]
24
+ attrs
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,32 +1,44 @@
1
1
  module ActiveRecordImporter
2
2
  class BatchImporter
3
3
 
4
- attr_reader :data, :import
4
+ attr_reader :data, :import, :failed_file, :processor
5
5
 
6
6
  def initialize(import, data)
7
7
  @import = import
8
8
  @data = data
9
+ @failed_file = FailedFileBuilder.new(import)
9
10
  end
10
11
 
11
12
  def process!
12
13
  @imported_count, @failed_count = 0, 0
13
-
14
14
  data.each do |row|
15
15
  next if row.blank?
16
- processor = DataProcessor.new(import, row.symbolize_keys!)
17
- processor.process ? @imported_count += 1 : @failed_count += 1
16
+ process_row(row.symbolize_keys!)
18
17
  end
19
18
 
20
19
  set_import_count
20
+ finalize_batch_import
21
21
  end
22
22
 
23
23
  private
24
24
 
25
+ def process_row(row)
26
+ processor = DataProcessor.new(import, row)
27
+ return @imported_count += 1 if processor.process
28
+
29
+ @failed_file.failed_rows << row.merge(import_errors: processor.row_errors)
30
+ @failed_count += 1
31
+ end
32
+
25
33
  def set_import_count
26
34
  Import.update_counters(import.id, imported_rows: @imported_count)
27
35
  Import.update_counters(import.id, failed_rows: @failed_count)
28
36
  end
29
37
 
38
+ def finalize_batch_import
39
+ @failed_file.build
40
+ end
41
+
30
42
  def importable
31
43
  import.resource.safe_constantize
32
44
  end
@@ -38,7 +38,7 @@ module ActiveRecordImporter
38
38
  end
39
39
 
40
40
  def fetch_instance_attributes
41
- @attributes = AttributesBuilder.new(
41
+ @attributes = Attribute::AttributesBuilder.new(
42
42
  importable, row_attrs
43
43
  ).build
44
44
  rescue => exception
@@ -46,7 +46,7 @@ module ActiveRecordImporter
46
46
  end
47
47
 
48
48
  def fetch_find_attributes
49
- @find_attributes = FindOptionsBuilder.new(
49
+ @find_attributes = Attribute::FindOptionsBuilder.new(
50
50
  resource: import.resource,
51
51
  find_options: import.find_options,
52
52
  attrs: attributes
@@ -12,6 +12,10 @@ module ActiveRecordImporter
12
12
  end
13
13
  end
14
14
 
15
- class DuplicateRecord < StandardError; end
15
+ class DuplicateRecord < StandardError
16
+ def initialize
17
+ super 'Duplicate record found!'
18
+ end
19
+ end
16
20
  end
17
21
  end
@@ -0,0 +1,81 @@
1
+ module ActiveRecordImporter
2
+ class FailedFileBuilder
3
+ attr_reader :import
4
+ attr_accessor :failed_rows
5
+
6
+ def initialize(import)
7
+ @import = import
8
+ @failed_rows = []
9
+ end
10
+
11
+ def build
12
+ return if failed_rows.blank?
13
+
14
+ create_or_append_to_csv
15
+ create_import_failed_file
16
+ destroy_temp_file
17
+ end
18
+
19
+ private
20
+
21
+ def create_or_append_to_csv
22
+ puts 'TEST!!!'
23
+ if import.failed_file.present?
24
+ puts 'APPEND!!!!'
25
+ append_rows_to_file
26
+ else
27
+ puts 'WRITE!!!!'
28
+ write_csv_file
29
+ end
30
+ end
31
+
32
+ def write_csv_file
33
+ CSV.open(temp_file_path, 'wb') do |csv|
34
+ csv << failed_rows.first.keys
35
+ insert_failed_rows(csv)
36
+ end
37
+ end
38
+
39
+ def append_rows_to_file
40
+ return if import.failed_file.blank?
41
+ CSV.open(import.failed_file_path, 'a+') do |csv|
42
+ insert_failed_rows(csv)
43
+ end
44
+ end
45
+
46
+
47
+ def insert_failed_rows(csv)
48
+ failed_rows.each do |hash|
49
+ csv << hash.values
50
+ end
51
+ end
52
+
53
+ def destroy_temp_file
54
+ return unless File.exists?(temp_file_path)
55
+ FileUtils.rm(temp_file_path)
56
+ end
57
+
58
+ def temp_file_path
59
+ "/tmp/#{target_file_name}"
60
+ end
61
+
62
+ def target_file_name
63
+ "failed_file_#{import.id}.csv"
64
+ end
65
+
66
+ def create_import_failed_file
67
+ return if import.failed_file.present?
68
+ File.open(temp_file_path) do |file|
69
+ import.failed_file = file
70
+
71
+ # I forced to save it as 'text/csv' because
72
+ # the file is being saved as 'text/x-pascal'
73
+ # and I still have no idea why?!?
74
+
75
+ import.failed_file_content_type = 'text/csv'
76
+ import.save!
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordImporter
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -8,6 +8,7 @@ module ActiveRecordImporter
8
8
  default: :upsert
9
9
 
10
10
  has_attached_file :file
11
+ has_attached_file :failed_file
11
12
 
12
13
  attr_accessor :execute_on_create
13
14
 
@@ -18,15 +19,22 @@ module ActiveRecordImporter
18
19
  content_type: %w(text/plain text/csv)
19
20
  }
20
21
 
21
- after_create :execute, if: :execute_on_create
22
+ validates_attachment :failed_file,
23
+ content_type: {
24
+ content_type: %w(text/plain text/csv)
25
+ }
22
26
 
23
- # I'll add import options in the next release
27
+ # I'll add import options in the next major release
24
28
  # accepts_nested_attributes_for :import_options, allow_destroy: true
25
29
 
26
30
  def execute
27
31
  resource_class.import!(self, execute_on_create)
28
32
  end
29
33
 
34
+ def execute!
35
+ resource_class.import!(self, true)
36
+ end
37
+
30
38
  def resource_class
31
39
  resource.safe_constantize
32
40
  end
@@ -37,11 +45,20 @@ module ActiveRecordImporter
37
45
 
38
46
  ##
39
47
  # Override this if you prefer have
40
- # a private permissions or you have
48
+ # private permissions or you have
41
49
  # private methods for reading files
42
50
  ##
43
51
  def import_file
44
- local_path? ? file.path : file.url
52
+ local_path?(file) ? file.path : file.url
53
+ end
54
+
55
+ ##
56
+ # Override this method if you have
57
+ # private permissions or you have private methods
58
+ # for reading/writing uploaded files
59
+ ##
60
+ def failed_file_path
61
+ local_path?(failed_file) ? failed_file.path : failed_file.url
45
62
  end
46
63
 
47
64
  private
@@ -51,8 +68,8 @@ module ActiveRecordImporter
51
68
  errors.add(:find_options, "can't be blank") if find_options.blank?
52
69
  end
53
70
 
54
- def local_path?
55
- File.exist? import_file.file.path
71
+ def local_path?(f)
72
+ File.exist? f.path
56
73
  end
57
74
  end
58
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_importer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Nera
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-23 00:00:00.000000000 Z
11
+ date: 2017-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -152,14 +152,15 @@ files:
152
152
  - bin/setup
153
153
  - db/migrate/1_active_record_importer_migration.rb
154
154
  - lib/active_record_importer.rb
155
- - lib/active_record_importer/attributes_builder.rb
155
+ - lib/active_record_importer/attribute/attributes_builder.rb
156
+ - lib/active_record_importer/attribute/find_options_builder.rb
157
+ - lib/active_record_importer/attribute/helpers.rb
156
158
  - lib/active_record_importer/batch_importer.rb
157
159
  - lib/active_record_importer/data_processor.rb
158
160
  - lib/active_record_importer/dispatcher.rb
159
161
  - lib/active_record_importer/engine.rb
160
162
  - lib/active_record_importer/errors.rb
161
- - lib/active_record_importer/find_options_builder.rb
162
- - lib/active_record_importer/helpers.rb
163
+ - lib/active_record_importer/failed_file_builder.rb
163
164
  - lib/active_record_importer/import_callbacker.rb
164
165
  - lib/active_record_importer/importable.rb
165
166
  - lib/active_record_importer/instance_builder.rb
@@ -1,67 +0,0 @@
1
- module ActiveRecordImporter
2
- class AttributesBuilder
3
- include Helpers
4
-
5
- attr_reader :importable, :row_attrs, :processed_attrs
6
-
7
- delegate :importer_options, to: :importable
8
- delegate :importable_columns,
9
- :default_attributes,
10
- :find_assoc_opts,
11
- to: :importer_options
12
-
13
- def initialize(importable, row_attrs)
14
- @importable = importable
15
- @row_attrs = row_attrs
16
- @processed_attrs = {}
17
- end
18
-
19
- def build
20
- force_encode_attributes
21
- fetch_time_attributes
22
- processed_attrs
23
- end
24
-
25
- private
26
-
27
- def default_attrs
28
- def_attrs = { importing: true }
29
- default_attributes.each do |key, value|
30
- def_attrs[key] = fetch_value(value)
31
- end
32
- end
33
-
34
- def force_encode_attributes
35
- @processed_attrs = force_utf8_encode(merged_attributes)
36
- end
37
-
38
- def fetch_time_attributes
39
- @processed_attrs.merge!(time_attributes(processed_attrs))
40
- end
41
-
42
- def fetch_value(value)
43
- case value
44
- when Proc
45
- value.call(row_attrs)
46
- when Symbol
47
- importable.send(value, row_attrs)
48
- else
49
- value
50
- end
51
- end
52
-
53
- def merged_attributes
54
- attributes = row_attrs.slice(*importable_columns)
55
- row_attrs = attributes.inject({}) do |row_attrs, key_value|
56
- row_attrs[key_value.first] = key_value.last if key_value.last.present?
57
- row_attrs
58
- end
59
- default_attrs.merge(row_attrs)
60
- end
61
-
62
- def has_column?(column)
63
- return if column.blank?
64
- importable_columns.include?(column.to_sym)
65
- end
66
- end
67
- end
@@ -1,52 +0,0 @@
1
- module ActiveRecordImporter
2
- class FindOptionsBuilder
3
- include Virtus.model
4
- include Helpers
5
-
6
- attribute :resource, String
7
- attribute :attrs, Hash, default: {}
8
- attribute :find_options, String
9
- attribute :prefix, String
10
-
11
- def build
12
- get_find_opts
13
- slice_attributes
14
- end
15
-
16
- private
17
-
18
- def klass
19
- resource.safe_constantize
20
- end
21
-
22
- delegate :importer_options, to: :klass
23
-
24
- delegate :required_attributes, to: :importer_options
25
-
26
- def get_find_opts
27
- @options = strip_and_symbolize
28
- @options ||= importer_options.find_options || required_attributes
29
- @options
30
- end
31
-
32
- def slice_attributes
33
- return attrs.slice(*@options).compact if prefix.blank?
34
-
35
- @options.inject({}) do |attr, key|
36
- attr[key] = attrs[prefixed_key(key)].presence
37
- attr
38
- end.compact
39
- end
40
-
41
- def prefixed_key(key)
42
- "#{prefix}#{key}".to_sym
43
- end
44
-
45
- def strip_and_symbolize
46
- return if find_options.blank?
47
- find_options.split(',').map do |key|
48
- key.strip.to_sym
49
- end
50
- end
51
- end
52
- end
@@ -1,26 +0,0 @@
1
- module ActiveRecordImporter
2
- module Helpers
3
-
4
- def parse_datetime(datetime = nil)
5
- return if datetime.blank?
6
- Time.parse(datetime)
7
- end
8
-
9
- def force_utf8_encode(data = {})
10
- return data if data.blank?
11
-
12
- data.keys.each do |key|
13
- data[key] = data[key].force_encoding('UTF-8') if data[key].is_a?(String)
14
- end
15
-
16
- data
17
- end
18
-
19
- def time_attributes(data = {})
20
- attrs = {}
21
- attrs[:created_at] = parse_datetime(data[:created_at]) || Time.now
22
- attrs[:updated_at] = parse_datetime(data[:updated_at]) || attrs[:created_at]
23
- attrs
24
- end
25
- end
26
- end