act_as_importable 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ act_as_importable.db
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
- results = csv.map do |row|
17
- row = row.to_hash.with_indifferent_access
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 = options.reverse_merge!(@default_import_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 = find_or_create_by(uid_values(row, options))
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
- row = row.reject { |key, value| Array(options[:except]).include? key } if options[:except]
42
- row = row.select { |key, value| Array(options[:only]).include? key } if options[:only]
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 update_associations(record, row)
57
- row.each_key do |key|
58
- key = key.to_s
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 update_association(record, key, value)
67
- association_name = key.split('.').first
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 find_association_value_with_attribute(name, attribute)
74
- association = self.reflect_on_association(name.to_sym)
75
- association.klass.where(attribute).first
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
- # Note: This will be available by default in rails 4
79
- def find_or_create_by(attributes, &block)
80
- find_by(attributes) || create(attributes, &block)
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
- # Note: This will by provided by default in rails 4
84
- def find_by(attributes)
85
- where(attributes).first
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
@@ -1,3 +1,3 @@
1
1
  module ActAsImportable
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  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
- before :each do
80
- @existing_item = Item.create!(:name => 'Beer', :price => 1.0)
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
- it "should update an existing record with matching unique identifier" do
83
- Item.import_record(row, :uid => 'name')
84
- @existing_item.reload.price.should == 2.5
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
- it "should not create a new item" do
87
- expect { Item.import_record(row, :uid => 'name') }.to change { Item.count }.by(0)
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 == {:name => 'Beer', :price => 2.5}
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 == {:name => 'Beer'}
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 == {:price => 2.5}
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
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-07 00:00:00.000000000 Z
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/act_as_importable.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
Binary file