act_as_importable 0.0.1 → 0.0.2
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.
- data/.rvmrc +1 -0
- data/act_as_importable.gemspec +2 -2
- data/db/act_as_importable.db +0 -0
- data/lib/act_as_importable/base.rb +41 -48
- data/lib/act_as_importable/config.rb +9 -1
- data/lib/act_as_importable/railtie.rb +12 -0
- data/lib/act_as_importable/version.rb +1 -1
- data/spec/act_as_importable/act_as_importable_spec.rb +17 -10
- metadata +7 -4
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.3@act_as_importable
|
data/act_as_importable.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = ActAsImportable::VERSION
|
9
9
|
gem.authors = ["Colin Harris"]
|
10
10
|
gem.email = ["col.w.harris@gmail.com"]
|
11
|
-
gem.description = %q{Helps import
|
12
|
-
gem.summary = %q{Helps import
|
11
|
+
gem.description = %q{Helps import records from CSV files.}
|
12
|
+
gem.summary = %q{Helps import records from CSV files.}
|
13
13
|
gem.homepage = "https://github.com/Col/act_as_importable"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
Binary file
|
@@ -5,29 +5,31 @@ module ActAsImportable::Base
|
|
5
5
|
|
6
6
|
module ClassMethods
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
# Existing records are found by the :uid.
|
11
|
-
# This can be changed by providing the :unique_identifier option or overriding the default_unique_identifier class method.
|
12
|
-
def import_file(file, options = {})
|
13
|
-
import_text(File.read(file), options)
|
8
|
+
def import_csv_file(file, options = {})
|
9
|
+
import_csv_text(File.read(file), options)
|
14
10
|
end
|
15
11
|
|
16
|
-
|
17
|
-
# Imports csv text into a model
|
18
|
-
def import_text(text, options = {})
|
12
|
+
def import_csv_text(text, options = {})
|
19
13
|
csv = ::CSV.parse(text, :headers => true)
|
20
|
-
csv.
|
14
|
+
results = csv.map do |row|
|
21
15
|
row = row.to_hash.with_indifferent_access
|
22
16
|
import_record(row, options)
|
23
17
|
end
|
24
18
|
end
|
25
19
|
|
20
|
+
# Creates or updates a model record
|
21
|
+
# Existing records are found by the column(s) specified by the :uid option (default 'id').
|
22
|
+
# If the values for the uid columns are not provided the row will be ignored.
|
23
|
+
# If uid is set to nil it will import the row data as a new record.
|
26
24
|
def import_record(row, options = {})
|
25
|
+
options = options.reverse_merge!(@default_import_options)
|
27
26
|
row = filter_columns(row, options)
|
28
|
-
record =
|
29
|
-
|
30
|
-
record
|
27
|
+
record = find_or_create_by(uid_values(row, options))
|
28
|
+
remove_uid_values_from_row(row, options)
|
29
|
+
update_record(record, row)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_record(record, row)
|
31
33
|
update_associations(record, row)
|
32
34
|
record.update_attributes(row)
|
33
35
|
record.save!
|
@@ -39,55 +41,46 @@ module ActAsImportable::Base
|
|
39
41
|
row
|
40
42
|
end
|
41
43
|
|
42
|
-
def
|
43
|
-
Array(options[:
|
44
|
+
def uid_values(row, options)
|
45
|
+
Hash[Array(options[:uid]).map { |k| [k, row[k.to_sym]] }]
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_uid_values_from_row(row, options = {})
|
49
|
+
Array(options[:uid]).each do |field|
|
44
50
|
row.delete(field)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
|
49
|
-
# Updates any associations specified in the import data.
|
50
|
-
# Associations are specified in the header by separating the association and the field by a '.'
|
51
|
-
def update_associations(existing, row)
|
54
|
+
def update_associations(record, row)
|
52
55
|
row.each_key do |key|
|
53
56
|
key = key.to_s
|
54
57
|
if key.include?('.')
|
55
|
-
|
56
|
-
field = key.split('.').last
|
57
|
-
association = self.reflect_on_association(association_name.to_sym)
|
58
|
-
association_value = association.klass.where("#{field} = ?", row[key]).first
|
59
|
-
existing.send("#{association_name}=", association_value) if association_value
|
58
|
+
update_association(record, key, row[key])
|
60
59
|
row.delete(key)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
fields = Array(options[:unique_identifier])
|
71
|
-
fields.inject(self.scoped.readonly(false)) { |scope, field|
|
72
|
-
add_scope_for_field(scope, field.to_s, row)
|
73
|
-
}.first
|
64
|
+
def update_association(record, key, value)
|
65
|
+
association_name = key.split('.').first
|
66
|
+
uid_field = key.split('.').last
|
67
|
+
value = find_association_value_with_attribute(association_name, uid_field => value)
|
68
|
+
record.send("#{association_name}=", value) if value
|
74
69
|
end
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
scope.where("#{field} = ?", value)
|
90
|
-
end
|
71
|
+
def find_association_value_with_attribute(name, attribute)
|
72
|
+
association = self.reflect_on_association(name.to_sym)
|
73
|
+
association.klass.where(attribute).first
|
74
|
+
end
|
75
|
+
|
76
|
+
# Note: This will be available by default in rails 4
|
77
|
+
def find_or_create_by(attributes, &block)
|
78
|
+
find_by(attributes) || create(attributes, &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Note: This will by provided by default in rails 4
|
82
|
+
def find_by(attributes)
|
83
|
+
where(attributes).first
|
91
84
|
end
|
92
85
|
|
93
86
|
end
|
@@ -4,8 +4,16 @@ module ActAsImportable::Config
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
module ClassMethods
|
7
|
-
def act_as_importable
|
7
|
+
def act_as_importable(options = {})
|
8
8
|
include ActAsImportable::Base
|
9
|
+
|
10
|
+
@default_import_options = options
|
11
|
+
@default_import_options[:uid] ||= :id
|
12
|
+
|
13
|
+
# create a reader on the class to access the field name
|
14
|
+
class << self;
|
15
|
+
attr_reader :default_import_options
|
16
|
+
end
|
9
17
|
end
|
10
18
|
end
|
11
19
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'act_as_importable'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ActAsImportable
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
initializer "act_as_importable.active_record" do |app|
|
7
|
+
ActiveSupport.on_load :active_record do
|
8
|
+
ActiveRecord::Base.send :include, ActAsImportable::Config
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -18,7 +18,7 @@ ActiveRecord::Base.connection.create_table(:items) do |t|
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class Item < ActiveRecord::Base
|
21
|
-
act_as_importable
|
21
|
+
act_as_importable :uid => 'name'
|
22
22
|
|
23
23
|
belongs_to :category
|
24
24
|
end
|
@@ -37,15 +37,22 @@ describe "an act_as_importable model" do
|
|
37
37
|
|
38
38
|
subject { Item }
|
39
39
|
|
40
|
-
it { should respond_to :
|
41
|
-
it { should respond_to :
|
40
|
+
it { should respond_to :import_csv_file }
|
41
|
+
it { should respond_to :import_csv_text }
|
42
42
|
it { should respond_to :import_record }
|
43
|
+
it { should respond_to :default_import_options }
|
44
|
+
|
45
|
+
let(:default_options) { {:uid => 'name'} }
|
46
|
+
|
47
|
+
it "should have the correct default import options" do
|
48
|
+
Item.default_import_options.should == default_options
|
49
|
+
end
|
43
50
|
|
44
51
|
describe "import csv file" do
|
45
52
|
let(:file) { 'spec/fixtures/items.csv' }
|
46
|
-
it 'should call import_text with
|
47
|
-
Item.should_receive(:
|
48
|
-
Item.
|
53
|
+
it 'should call import_text with contents of file' do
|
54
|
+
Item.should_receive(:import_csv_text).with(File.read(file), {})
|
55
|
+
Item.import_csv_file(file)
|
49
56
|
end
|
50
57
|
end
|
51
58
|
|
@@ -54,7 +61,7 @@ describe "an act_as_importable model" do
|
|
54
61
|
it 'should call import_record with row hashes' do
|
55
62
|
Item.should_receive(:import_record).with({'name' => 'Beer', 'price' => '2.5'}, {}).once
|
56
63
|
Item.should_receive(:import_record).with({'name' => 'Apple', 'price' => '0.5'}, {}).once
|
57
|
-
Item.
|
64
|
+
Item.import_csv_text(text)
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
@@ -70,11 +77,11 @@ describe "an act_as_importable model" do
|
|
70
77
|
@existing_item = Item.create!(:name => 'Beer', :price => 1.0)
|
71
78
|
end
|
72
79
|
it "should update an existing record with matching unique identifier" do
|
73
|
-
Item.import_record(row, :
|
80
|
+
Item.import_record(row, :uid => 'name')
|
74
81
|
@existing_item.reload.price.should == 2.5
|
75
82
|
end
|
76
83
|
it "should not create a new item" do
|
77
|
-
expect { Item.import_record(row, :
|
84
|
+
expect { Item.import_record(row, :uid => 'name') }.to change { Item.count }.by(0)
|
78
85
|
end
|
79
86
|
end
|
80
87
|
end
|
@@ -94,7 +101,7 @@ describe "an act_as_importable model" do
|
|
94
101
|
let(:row) { {:name => 'Beer', :price => 2.5} }
|
95
102
|
|
96
103
|
it 'should filter columns when importing each record' do
|
97
|
-
Item.should_receive(:filter_columns).with(row,
|
104
|
+
Item.should_receive(:filter_columns).with(row, default_options).and_return(row)
|
98
105
|
Item.import_record(row)
|
99
106
|
end
|
100
107
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: act_as_importable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -75,7 +75,7 @@ dependencies:
|
|
75
75
|
- - ! '>='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
|
-
description: Helps import
|
78
|
+
description: Helps import records from CSV files.
|
79
79
|
email:
|
80
80
|
- col.w.harris@gmail.com
|
81
81
|
executables: []
|
@@ -83,14 +83,17 @@ extensions: []
|
|
83
83
|
extra_rdoc_files: []
|
84
84
|
files:
|
85
85
|
- .gitignore
|
86
|
+
- .rvmrc
|
86
87
|
- Gemfile
|
87
88
|
- LICENSE.txt
|
88
89
|
- README.md
|
89
90
|
- Rakefile
|
90
91
|
- act_as_importable.gemspec
|
92
|
+
- db/act_as_importable.db
|
91
93
|
- lib/act_as_importable.rb
|
92
94
|
- lib/act_as_importable/base.rb
|
93
95
|
- lib/act_as_importable/config.rb
|
96
|
+
- lib/act_as_importable/railtie.rb
|
94
97
|
- lib/act_as_importable/version.rb
|
95
98
|
- spec/act_as_importable/act_as_importable_spec.rb
|
96
99
|
- spec/fixtures/items.csv
|
@@ -118,7 +121,7 @@ rubyforge_project:
|
|
118
121
|
rubygems_version: 1.8.24
|
119
122
|
signing_key:
|
120
123
|
specification_version: 3
|
121
|
-
summary: Helps import
|
124
|
+
summary: Helps import records from CSV files.
|
122
125
|
test_files:
|
123
126
|
- spec/act_as_importable/act_as_importable_spec.rb
|
124
127
|
- spec/fixtures/items.csv
|