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 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