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 ADDED
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.3@act_as_importable
@@ -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 models from CSV files.}
12
- gem.summary = %q{Helps import models from CSV files.}
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
- # Imports a data file into a model
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.each do |row|
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 = find_existing_record(row, options)
29
- remove_unique_identifiers(row, options) if record
30
- record ||= self.new()
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 remove_unique_identifiers(row, options = {})
43
- Array(options[:unique_identifier]).each do |field|
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
- association_name = key.split('.').first
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
- # Fetches the existing record based on the identifier field(s) specified by the :unique_identifier option.
67
- # Defaults to the field specified by 'default_unique_identifier'
68
- def find_existing_record(row, options = {})
69
- return unless options[:unique_identifier]
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
- # Refines an existing scope to include a new field
78
- # The field can traverse 'belongs_to' associations by joining the association and field with a '.'
79
- # The query value should be found within the hash with the field as the key.
80
- # Notes: currently only supports field paths through one association.
81
- def add_scope_for_field(scope, field, hash)
82
- value = hash[field]
83
- return scope unless value
84
- if field.include?('.')
85
- association = field.split('.').first
86
- field_name = field.split('.').last
87
- scope.joins(association.to_sym).where("#{association.pluralize}.#{field_name} = ?", value)
88
- else
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
@@ -1,3 +1,3 @@
1
1
  module ActAsImportable
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  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 :import_file }
41
- it { should respond_to :import_text }
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 context of file' do
47
- Item.should_receive(:import_text).with(File.read(file), {})
48
- Item.import_file(file)
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.import_text(text)
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, :unique_identifier => 'name')
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, :unique_identifier => 'name') }.to change { Item.count }.by(0)
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, {}).and_return(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.1
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-05 00:00:00.000000000 Z
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 models from CSV files.
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 models from CSV files.
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