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