act_as_importable 0.0.4 → 0.0.5
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/.gitignore +1 -0
- data/db/.gitkeep +0 -0
- data/lib/act_as_importable/base.rb +36 -35
- data/lib/act_as_importable/version.rb +1 -1
- data/spec/act_as_importable/act_as_importable_spec.rb +92 -12
- metadata +3 -3
- data/db/act_as_importable.db +0 -0
data/.gitignore
CHANGED
data/db/.gitkeep
ADDED
File without changes
|
@@ -13,9 +13,8 @@ module ActAsImportable
|
|
13
13
|
|
14
14
|
def import_csv_text(text, options = {})
|
15
15
|
csv = ::CSV.parse(text, :headers => true)
|
16
|
-
|
17
|
-
row
|
18
|
-
import_record(row, options)
|
16
|
+
csv.map do |row|
|
17
|
+
import_record(row.to_hash, options)
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
@@ -24,22 +23,22 @@ module ActAsImportable
|
|
24
23
|
# If the values for the uid columns are not provided the row will be ignored.
|
25
24
|
# If uid is set to nil it will import the row data as a new record.
|
26
25
|
def import_record(row, options = {})
|
27
|
-
options
|
26
|
+
options.reverse_merge!(@default_import_options)
|
27
|
+
row = row.with_indifferent_access
|
28
|
+
row.reverse_merge!(options[:default_values]) if options[:default_values]
|
29
|
+
convert_key_paths_to_values!(row)
|
28
30
|
row = filter_columns(row, options)
|
29
|
-
record =
|
31
|
+
record = find_or_create_by_uids(uid_values(row, options))
|
30
32
|
remove_uid_values_from_row(row, options)
|
31
|
-
update_record(record, row)
|
32
|
-
end
|
33
|
-
|
34
|
-
def update_record(record, row)
|
35
|
-
update_associations(record, row)
|
36
33
|
record.update_attributes(row)
|
37
|
-
record.save
|
34
|
+
record.save
|
38
35
|
end
|
39
36
|
|
40
37
|
def filter_columns(row, options = {})
|
41
|
-
|
42
|
-
|
38
|
+
except = Array(options[:except]).map { |i| i.to_s }
|
39
|
+
only = Array(options[:only]).map { |i| i.to_s }
|
40
|
+
row = row.reject { |key, value| except.include? key.to_s } if except.present?
|
41
|
+
row = row.select { |key, value| only.include? key.to_s } if only.present?
|
43
42
|
row
|
44
43
|
end
|
45
44
|
|
@@ -53,36 +52,38 @@ module ActAsImportable
|
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
if key.include?('.')
|
60
|
-
update_association(record, key, row[key])
|
61
|
-
row.delete(key)
|
62
|
-
end
|
63
|
-
end
|
55
|
+
def find_association_value_with_attribute(name, attribute)
|
56
|
+
association = self.reflect_on_association(name.to_sym)
|
57
|
+
association.klass.where(attribute).first
|
64
58
|
end
|
65
59
|
|
66
|
-
def
|
67
|
-
|
68
|
-
uid_field = key.split('.').last
|
69
|
-
value = find_association_value_with_attribute(association_name, uid_field => value)
|
70
|
-
record.send("#{association_name}=", value) if value
|
60
|
+
def find_or_create_by_uids(attributes, &block)
|
61
|
+
find_by_uids(attributes) || create(attributes, &block)
|
71
62
|
end
|
72
63
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
64
|
+
def find_by_uids(attributes)
|
65
|
+
attributes.inject(self.scoped.readonly(false)) { |scope, key_value|
|
66
|
+
add_scope_for_field(scope, key_value[0].to_s, key_value[1])
|
67
|
+
}.first
|
76
68
|
end
|
77
69
|
|
78
|
-
|
79
|
-
|
80
|
-
|
70
|
+
def add_scope_for_field(scope, field, value)
|
71
|
+
return scope unless value
|
72
|
+
if (association = self.reflect_on_association(field.to_sym))
|
73
|
+
field = association.foreign_key
|
74
|
+
value = value.id
|
75
|
+
end
|
76
|
+
scope.where("#{self.table_name}.#{field} = ?", value)
|
81
77
|
end
|
82
78
|
|
83
|
-
#
|
84
|
-
def
|
85
|
-
|
79
|
+
# TODO: update this to support finding the association value with multiple columns
|
80
|
+
def convert_key_paths_to_values!(row)
|
81
|
+
key_path_attributes = row.select { |k,v| k.to_s.include? '.' }
|
82
|
+
key_path_attributes.each do |key, value|
|
83
|
+
association_name, uid_field = key.to_s.split('.')
|
84
|
+
row[association_name.to_sym] = find_association_value_with_attribute(association_name, uid_field => value)
|
85
|
+
row.delete(key.to_sym)
|
86
|
+
end
|
86
87
|
end
|
87
88
|
|
88
89
|
end
|
@@ -75,17 +75,53 @@ describe "an act_as_importable model" do
|
|
75
75
|
expect { Item.import_record(row) }.to change{Item.count}.by(1)
|
76
76
|
end
|
77
77
|
|
78
|
-
describe "unique identifier" do
|
79
|
-
|
80
|
-
|
78
|
+
describe "unique identifier (uid) option" do
|
79
|
+
|
80
|
+
context "record exists with matching uid" do
|
81
|
+
before :each do
|
82
|
+
@existing_item = Item.create!(:name => 'Beer', :price => 1.0)
|
83
|
+
end
|
84
|
+
it "should update an existing record with matching uid" do
|
85
|
+
Item.import_record(row, :uid => :name)
|
86
|
+
@existing_item.reload.price.should == 2.5
|
87
|
+
end
|
88
|
+
it "should not create a new item" do
|
89
|
+
expect { Item.import_record(row, :uid => 'name') }.to change { Item.count }.by(0)
|
90
|
+
end
|
81
91
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
92
|
+
|
93
|
+
context "record doesn't exist with matching uid" do
|
94
|
+
it "should create a new record" do
|
95
|
+
expect { Item.import_record(row, :uid => :name) }.to change { Item.count }.by(1)
|
96
|
+
end
|
97
|
+
it "should assign the uid values to the record" do
|
98
|
+
Item.import_record(row, :uid => :name)
|
99
|
+
Item.first.name.should == 'Beer'
|
100
|
+
end
|
85
101
|
end
|
86
|
-
|
87
|
-
|
102
|
+
|
103
|
+
context "support for multiple uid columns" do
|
104
|
+
let(:category1) { Category.create!(:name => 'Beverage') }
|
105
|
+
let(:category2) { Category.create!(:name => 'Beers') }
|
106
|
+
let(:row) { { :name => 'Beer', :'category.name' => category2.name, :price => 3.2}}
|
107
|
+
before :each do
|
108
|
+
@existing1 = Item.create!(:name => 'Beer', :price => 1.0, :category => category1)
|
109
|
+
@existing2 = Item.create!(:name => 'Beer', :price => 2.5, :category => category2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should match existing records with multiple uid columns" do
|
113
|
+
Item.import_record(row, :uid => [:name, :category])
|
114
|
+
@existing2.reload.price.should == 3.2
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should allow uid column value to come from default values" do
|
118
|
+
row = { :name => 'Beer', :price => 3.2 }
|
119
|
+
Item.import_record(row, :uid => [:name, :category], :default_values => { :'category.name' => category2.name })
|
120
|
+
@existing2.reload.price.should == 3.2
|
121
|
+
end
|
122
|
+
|
88
123
|
end
|
124
|
+
|
89
125
|
end
|
90
126
|
end
|
91
127
|
|
@@ -100,8 +136,52 @@ describe "an act_as_importable model" do
|
|
100
136
|
end
|
101
137
|
end
|
102
138
|
|
139
|
+
describe "default_values option" do
|
140
|
+
let(:row) { {:name => 'Beer'} }
|
141
|
+
|
142
|
+
it "should assign default values to new record" do
|
143
|
+
Item.import_record(row, :default_values => { :price => 3.2 })
|
144
|
+
Item.first.price.should == 3.2
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should assign default values to updated record" do
|
148
|
+
Item.create!(:name => 'Beer', :price => 1.0)
|
149
|
+
Item.import_record(row, :uid => :name, :default_values => {:price => 3.2})
|
150
|
+
Item.first.price.should == 3.2
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should not override row values" do
|
154
|
+
row[:price] = 4.1
|
155
|
+
Item.import_record(row, :default_values => {:price => 3.2})
|
156
|
+
Item.first.price.should == 4.1
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "associations" do
|
160
|
+
let(:category) { Category.new(:name => 'Beverage') }
|
161
|
+
|
162
|
+
it "should assign default association value to new record" do
|
163
|
+
Item.import_record(row, :default_values => {:category => category})
|
164
|
+
Item.first.category.should == category
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should assign default association value to updated record" do
|
168
|
+
Item.create!(:name => 'Beer', :price => 1.0)
|
169
|
+
Item.import_record(row, :uid => :name, :default_values => {:category => category})
|
170
|
+
Item.first.category.should == category
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should not override row values" do
|
174
|
+
other_category = Category.create!(:name => 'Food & Beverage')
|
175
|
+
row['category.name'] = other_category.name
|
176
|
+
Item.import_record(row, :default_values => {'category.name' => category.name})
|
177
|
+
Item.first.category.should == other_category
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
103
183
|
describe "#filter_columns" do
|
104
|
-
let(:row) { {:name => 'Beer', :price => 2.5} }
|
184
|
+
let(:row) { {:name => 'Beer', :price => 2.5}.with_indifferent_access }
|
105
185
|
|
106
186
|
it 'should filter columns when importing each record' do
|
107
187
|
Item.should_receive(:filter_columns).with(row, default_options).and_return(row)
|
@@ -109,15 +189,15 @@ describe "an act_as_importable model" do
|
|
109
189
|
end
|
110
190
|
|
111
191
|
it "should not modify row if no options provided" do
|
112
|
-
Item.filter_columns(row).should ==
|
192
|
+
Item.filter_columns(row).should == row
|
113
193
|
end
|
114
194
|
|
115
195
|
it "should remove columns specified by the :except option" do
|
116
|
-
Item.filter_columns(row, :except => :price).should == {
|
196
|
+
Item.filter_columns(row, :except => :price).should == {'name' => 'Beer'}
|
117
197
|
end
|
118
198
|
|
119
199
|
it "should remove columns not specified by the :only option" do
|
120
|
-
Item.filter_columns(row, :only => :price).should == {
|
200
|
+
Item.filter_columns(row, :only => :price).should == {'price' => 2.5}
|
121
201
|
end
|
122
202
|
end
|
123
203
|
|
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.5
|
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-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -89,7 +89,7 @@ files:
|
|
89
89
|
- README.md
|
90
90
|
- Rakefile
|
91
91
|
- act_as_importable.gemspec
|
92
|
-
- db
|
92
|
+
- db/.gitkeep
|
93
93
|
- lib/act_as_importable.rb
|
94
94
|
- lib/act_as_importable/base.rb
|
95
95
|
- lib/act_as_importable/config.rb
|
data/db/act_as_importable.db
DELETED
Binary file
|