importer 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -14
- data/Rakefile +6 -8
- data/VERSION +1 -1
- data/importer.gemspec +26 -39
- data/lib/importer.rb +11 -70
- data/lib/importer/adapters/active_record_adapter.rb +102 -0
- data/lib/importer/adapters/mongo_mapper_adapter.rb +104 -0
- data/lib/importer/import.rb +39 -1
- data/lib/importer/imported_object.rb +29 -2
- data/lib/importer/parser/base.rb +4 -0
- data/lib/importer/parser/csv.rb +2 -2
- data/lib/importer/parser/xml.rb +2 -2
- data/test/factories.rb +0 -9
- data/test/helper.rb +14 -48
- data/test/importer/adapters/active_record_adapter_test.rb +114 -0
- data/test/importer/adapters/mongo_mapper_adapter_test.rb +115 -0
- data/test/importer/import_test.rb +45 -0
- data/test/importer/imported_object_test.rb +18 -0
- metadata +20 -47
- data/lib/importer/import/active_record.rb +0 -41
- data/lib/importer/import/simple.rb +0 -61
- data/lib/importer/imported_object/active_record.rb +0 -41
- data/lib/importer/imported_object/simple.rb +0 -25
- data/rails_generators/importer/importer_generator.rb +0 -18
- data/rails_generators/importer/templates/imported_objects_migration.rb +0 -19
- data/rails_generators/importer/templates/imports_migration.rb +0 -17
- data/test/importer/import/active_record_test.rb +0 -36
- data/test/importer/import/simple_test.rb +0 -36
- data/test/importer/imported_object/active_record_test.rb +0 -37
- data/test/importer/imported_object/simple_test.rb +0 -35
- data/test/importer_test.rb +0 -50
data/README.rdoc
CHANGED
@@ -12,7 +12,6 @@ Docs: http://rdoc.info/projects/szajbus/importer
|
|
12
12
|
|
13
13
|
* can import from XML and CSV formats, but it's possible to add custom parsers
|
14
14
|
* reports how many new objects got imported, how many objects was modified and how many objects were invalid
|
15
|
-
* can save reports to the database, along with errors for later inspection
|
16
15
|
|
17
16
|
== Installation
|
18
17
|
|
@@ -24,11 +23,6 @@ Or the plugin
|
|
24
23
|
|
25
24
|
script/plugin install git://github.com/szajbus/importer.git
|
26
25
|
|
27
|
-
Generate the migrations and run them (this step may not be necessary, read below)
|
28
|
-
|
29
|
-
script/generate importer import
|
30
|
-
rake db:migrate
|
31
|
-
|
32
26
|
Add to your model
|
33
27
|
|
34
28
|
class Product < ActiveRecord::Base
|
@@ -39,21 +33,17 @@ And start importing
|
|
39
33
|
|
40
34
|
Product.import(path_to_xml_or_csv_file)
|
41
35
|
|
42
|
-
This will parse the file and import all products there are defined in it.
|
43
|
-
|
44
|
-
If you don't want to save summaries to database you can force the importer to do a simple import with:
|
45
|
-
|
46
|
-
Product.import(path_to_xml_or_csv_file, :import => Importer::Import::Simple.create)
|
36
|
+
This will parse the file and import all products there are defined in it. This will return import summary which will tell you how many products were created, modified or invalid. Exact information about each product (detected attributes and errors) will be available in summary too.
|
47
37
|
|
48
38
|
== Customization
|
49
39
|
|
50
40
|
You can create your own parser to import from sources other than XML or CSV files. Check the implementation of one of existing parsers to find out how to write your own. Then just pass parser class to import method:
|
51
41
|
|
52
|
-
Product.import(
|
42
|
+
Product.import(path_to_file, :parser => CustomParserClass)
|
53
43
|
|
54
|
-
You can also create your custom Import and ImportedObject classes
|
44
|
+
You can also create your custom versions of Import and ImportedObject classes. A possible alternative version could be ActiveRecord Import and ImportedObject models that would save import summary to database for later inspection. Check the rdocs for these classes for more information. You force the importer to use a custom Import class with:
|
55
45
|
|
56
|
-
Product.import(
|
46
|
+
Product.import(path_to_file, :import => CustomImportClass)
|
57
47
|
|
58
48
|
== Note on Patches/Pull Requests
|
59
49
|
|
@@ -65,6 +55,12 @@ You can also create your custom Import and ImportedObject classes if you need th
|
|
65
55
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
66
56
|
* Send me a pull request. Bonus points for topic branches.
|
67
57
|
|
58
|
+
== Disclaimer
|
59
|
+
|
60
|
+
Importer gem/plugin was extracted from an actual Ruby on Rails application. It probably lacks some features or needs some polishing. Feel free to contribute.
|
61
|
+
|
62
|
+
The gem is still under development, backward compatibility can not be guaranteed (at least until it reaches 1.0 stable version).
|
63
|
+
|
68
64
|
== Copyright
|
69
65
|
|
70
66
|
Copyright (c) 2010 Michal Szajbe. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -5,18 +5,16 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "importer"
|
8
|
-
gem.summary = %Q{Import objects from
|
9
|
-
gem.description = %Q{Define new objects or modifications of existing ones in
|
8
|
+
gem.summary = %Q{Import objects from external files}
|
9
|
+
gem.description = %Q{Define new objects or modifications of existing ones in external file (xml, csv, etc) and import them to your application. Importer will not only import all the objects but also will give you detailed summary of the import process.}
|
10
10
|
gem.email = "michal.szajbe@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/szajbus/importer"
|
12
12
|
gem.authors = ["Michał Szajbe"]
|
13
|
-
gem.add_dependency "crack", ">= 0"
|
14
|
-
gem.add_dependency "fastercsv", ">= 0"
|
15
|
-
gem.
|
16
|
-
gem.
|
17
|
-
gem.add_dependency "workflow", ">= 0"
|
13
|
+
gem.add_dependency "crack", ">= 0.1.6"
|
14
|
+
gem.add_dependency "fastercsv", ">= 1.5.0"
|
15
|
+
gem.add_development_dependency "activerecord", ">= 0"
|
16
|
+
gem.add_development_dependency "mongo_mapper", ">= 0.7.0"
|
18
17
|
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
19
|
-
gem.add_development_dependency "factory_girl", ">= 0"
|
20
18
|
gem.add_development_dependency 'sqlite3-ruby'
|
21
19
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
22
20
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/importer.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{importer}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Micha\305\202 Szajbe"]
|
12
|
-
s.date = %q{2010-
|
13
|
-
s.description = %q{Define new objects or modifications of existing ones in
|
12
|
+
s.date = %q{2010-04-07}
|
13
|
+
s.description = %q{Define new objects or modifications of existing ones in external file (xml, csv, etc) and import them to your application. Importer will not only import all the objects but also will give you detailed summary of the import process.}
|
14
14
|
s.email = %q{michal.szajbe@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
@@ -25,20 +25,15 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"importer.gemspec",
|
27
27
|
"lib/importer.rb",
|
28
|
+
"lib/importer/adapters/active_record_adapter.rb",
|
29
|
+
"lib/importer/adapters/mongo_mapper_adapter.rb",
|
28
30
|
"lib/importer/import.rb",
|
29
|
-
"lib/importer/import/active_record.rb",
|
30
|
-
"lib/importer/import/simple.rb",
|
31
31
|
"lib/importer/imported_object.rb",
|
32
|
-
"lib/importer/imported_object/active_record.rb",
|
33
|
-
"lib/importer/imported_object/simple.rb",
|
34
32
|
"lib/importer/parser.rb",
|
35
33
|
"lib/importer/parser/base.rb",
|
36
34
|
"lib/importer/parser/csv.rb",
|
37
35
|
"lib/importer/parser/xml.rb",
|
38
36
|
"rails/init.rb",
|
39
|
-
"rails_generators/importer/importer_generator.rb",
|
40
|
-
"rails_generators/importer/templates/imported_objects_migration.rb",
|
41
|
-
"rails_generators/importer/templates/imports_migration.rb",
|
42
37
|
"test/database.yml",
|
43
38
|
"test/factories.rb",
|
44
39
|
"test/fixtures/empty.csv",
|
@@ -48,31 +43,29 @@ Gem::Specification.new do |s|
|
|
48
43
|
"test/fixtures/products.csv",
|
49
44
|
"test/fixtures/products.xml",
|
50
45
|
"test/helper.rb",
|
51
|
-
"test/importer/
|
52
|
-
"test/importer/
|
53
|
-
"test/importer/
|
54
|
-
"test/importer/
|
46
|
+
"test/importer/adapters/active_record_adapter_test.rb",
|
47
|
+
"test/importer/adapters/mongo_mapper_adapter_test.rb",
|
48
|
+
"test/importer/import_test.rb",
|
49
|
+
"test/importer/imported_object_test.rb",
|
55
50
|
"test/importer/parser/csv_test.rb",
|
56
51
|
"test/importer/parser/xml_test.rb",
|
57
|
-
"test/importer/parser_test.rb"
|
58
|
-
"test/importer_test.rb"
|
52
|
+
"test/importer/parser_test.rb"
|
59
53
|
]
|
60
54
|
s.homepage = %q{http://github.com/szajbus/importer}
|
61
55
|
s.rdoc_options = ["--charset=UTF-8"]
|
62
56
|
s.require_paths = ["lib"]
|
63
57
|
s.rubygems_version = %q{1.3.5}
|
64
|
-
s.summary = %q{Import objects from
|
58
|
+
s.summary = %q{Import objects from external files}
|
65
59
|
s.test_files = [
|
66
60
|
"test/factories.rb",
|
67
61
|
"test/helper.rb",
|
68
|
-
"test/importer/
|
69
|
-
"test/importer/
|
70
|
-
"test/importer/
|
71
|
-
"test/importer/
|
62
|
+
"test/importer/adapters/active_record_adapter_test.rb",
|
63
|
+
"test/importer/adapters/mongo_mapper_adapter_test.rb",
|
64
|
+
"test/importer/import_test.rb",
|
65
|
+
"test/importer/imported_object_test.rb",
|
72
66
|
"test/importer/parser/csv_test.rb",
|
73
67
|
"test/importer/parser/xml_test.rb",
|
74
|
-
"test/importer/parser_test.rb"
|
75
|
-
"test/importer_test.rb"
|
68
|
+
"test/importer/parser_test.rb"
|
76
69
|
]
|
77
70
|
|
78
71
|
if s.respond_to? :specification_version then
|
@@ -80,32 +73,26 @@ Gem::Specification.new do |s|
|
|
80
73
|
s.specification_version = 3
|
81
74
|
|
82
75
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
83
|
-
s.add_runtime_dependency(%q<crack>, [">= 0"])
|
84
|
-
s.add_runtime_dependency(%q<fastercsv>, [">= 0"])
|
85
|
-
s.
|
86
|
-
s.
|
87
|
-
s.add_runtime_dependency(%q<workflow>, [">= 0"])
|
76
|
+
s.add_runtime_dependency(%q<crack>, [">= 0.1.6"])
|
77
|
+
s.add_runtime_dependency(%q<fastercsv>, [">= 1.5.0"])
|
78
|
+
s.add_development_dependency(%q<activerecord>, [">= 0"])
|
79
|
+
s.add_development_dependency(%q<mongo_mapper>, [">= 0.7.0"])
|
88
80
|
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
89
|
-
s.add_development_dependency(%q<factory_girl>, [">= 0"])
|
90
81
|
s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
|
91
82
|
else
|
92
|
-
s.add_dependency(%q<crack>, [">= 0"])
|
93
|
-
s.add_dependency(%q<fastercsv>, [">= 0"])
|
83
|
+
s.add_dependency(%q<crack>, [">= 0.1.6"])
|
84
|
+
s.add_dependency(%q<fastercsv>, [">= 1.5.0"])
|
94
85
|
s.add_dependency(%q<activerecord>, [">= 0"])
|
95
|
-
s.add_dependency(%q<
|
96
|
-
s.add_dependency(%q<workflow>, [">= 0"])
|
86
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0.7.0"])
|
97
87
|
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
98
|
-
s.add_dependency(%q<factory_girl>, [">= 0"])
|
99
88
|
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
100
89
|
end
|
101
90
|
else
|
102
|
-
s.add_dependency(%q<crack>, [">= 0"])
|
103
|
-
s.add_dependency(%q<fastercsv>, [">= 0"])
|
91
|
+
s.add_dependency(%q<crack>, [">= 0.1.6"])
|
92
|
+
s.add_dependency(%q<fastercsv>, [">= 1.5.0"])
|
104
93
|
s.add_dependency(%q<activerecord>, [">= 0"])
|
105
|
-
s.add_dependency(%q<
|
106
|
-
s.add_dependency(%q<workflow>, [">= 0"])
|
94
|
+
s.add_dependency(%q<mongo_mapper>, [">= 0.7.0"])
|
107
95
|
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
108
|
-
s.add_dependency(%q<factory_girl>, [">= 0"])
|
109
96
|
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
|
110
97
|
end
|
111
98
|
end
|
data/lib/importer.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'importer/adapters/active_record_adapter'
|
2
|
+
require 'importer/adapters/mongo_mapper_adapter'
|
3
3
|
require 'importer/import'
|
4
4
|
require 'importer/imported_object'
|
5
|
-
require 'importer/import/active_record'
|
6
|
-
require 'importer/import/simple'
|
7
|
-
require 'importer/imported_object/active_record'
|
8
|
-
require 'importer/imported_object/simple'
|
9
5
|
require 'importer/parser'
|
10
6
|
require 'importer/parser/base'
|
11
7
|
require 'importer/parser/csv'
|
12
8
|
require 'importer/parser/xml'
|
13
9
|
|
14
|
-
# Importer
|
10
|
+
# Importer module provides your models with flexible API that makes it easier
|
11
|
+
# to import data from external sources.
|
15
12
|
#
|
16
13
|
# Usage:
|
17
14
|
#
|
@@ -27,74 +24,18 @@ require 'importer/parser/xml'
|
|
27
24
|
# Copyright:: Copyright (c) 2010 Michal Szajbe
|
28
25
|
# License:: check the LICENCE file
|
29
26
|
module Importer
|
27
|
+
class AdapterError < ::Exception; end
|
30
28
|
|
31
29
|
class << self
|
32
30
|
def included(base)
|
33
|
-
base.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# The import process is wrapped in a transaction, so if anything goes wrong there is no
|
40
|
-
# harm done.
|
41
|
-
# * +file+ - path to an XML or CVS file, you can also import from other data formats,
|
42
|
-
# but you also need to provide a custom parser to read it
|
43
|
-
# Possible options:
|
44
|
-
# * +parser+ - by default the parser is determined from file extension, but you can force
|
45
|
-
# the imported to use another one by passing it's class here
|
46
|
-
# * +import+ - by default importer tries to store import summary in database, so it uses
|
47
|
-
# ActiveRecord import, to use other import type pass it's instance here
|
48
|
-
def import(file, options = {})
|
49
|
-
import = options[:import] || Importer::Import::ActiveRecord.create
|
50
|
-
parser = options[:parser] || Importer::Parser.get_klass(file)
|
51
|
-
data = parser.run(file)
|
52
|
-
|
53
|
-
transaction do
|
54
|
-
import.start!
|
55
|
-
|
56
|
-
data.each do |attributes|
|
57
|
-
imported_object = import.build_imported_object
|
58
|
-
|
59
|
-
if object = find_on_import(import, attributes)
|
60
|
-
imported_object.state = "existing_object"
|
61
|
-
else
|
62
|
-
object = new
|
63
|
-
imported_object.state = "new_object"
|
64
|
-
end
|
65
|
-
|
66
|
-
imported_object.data = attributes
|
67
|
-
object.merge_attributes_on_import(import, attributes)
|
68
|
-
|
69
|
-
unless object.save
|
70
|
-
imported_object.state = "invalid_object"
|
71
|
-
imported_object.validation_errors = object.errors.full_messages
|
72
|
-
end
|
73
|
-
|
74
|
-
imported_object.object = object
|
75
|
-
imported_object.save
|
76
|
-
end
|
77
|
-
|
78
|
-
import.finish!
|
31
|
+
if base.respond_to?(:descends_from_active_record?) && base.descends_from_active_record?
|
32
|
+
base.send(:include, Importer::Adapters::ActiveRecordAdapter)
|
33
|
+
elsif defined?(MongoMapper) && (base.include?(MongoMapper::Document) || base.include?(MongoMapper::EmbeddedDocument))
|
34
|
+
base.send(:include, Importer::Adapters::MongoMapperAdapter)
|
35
|
+
else
|
36
|
+
raise AdapterError.new("Can't determine adapter for #{base.class} class.")
|
79
37
|
end
|
80
|
-
|
81
|
-
import
|
82
|
-
end
|
83
|
-
|
84
|
-
# Overload +find_on_import+ method to find existing objects while importing an object.
|
85
|
-
# This is used to determine wheter an imported should add a new object or just modify
|
86
|
-
# an existing one. By default it searches records by id.
|
87
|
-
def find_on_import(import, attributes)
|
88
|
-
find_by_id(attributes["id"])
|
89
38
|
end
|
90
39
|
end
|
91
40
|
|
92
|
-
module InstanceMethods
|
93
|
-
# Overload +merge_attributes_on_import+ method if you need the detected attributes
|
94
|
-
# merged to an object in some specific way. By default detected attributes are literally
|
95
|
-
# assigned to an object.
|
96
|
-
def merge_attributes_on_import(import, attributes)
|
97
|
-
self.attributes = attributes
|
98
|
-
end
|
99
|
-
end
|
100
41
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Importer
|
2
|
+
module Adapters
|
3
|
+
# Adapter for ActiveRecord models
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# class Product < ActiveRecord::Base
|
8
|
+
# include Importer
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Product.import(path_to_xml_or_csv_file)
|
12
|
+
#
|
13
|
+
# It sends the given file to a parser and then imports detected objects.
|
14
|
+
# Instead of simply inserting all detected objects to database, the importer
|
15
|
+
# tries to determine wheter a detected object already exists. If so, the object
|
16
|
+
# is only updated, otherwise a new object is created.
|
17
|
+
#
|
18
|
+
# To change the way how importer checks for existing objects (or to turn off this
|
19
|
+
# behavior completely) override +find_on_import+ method. The default behavior now
|
20
|
+
# is to try to find existing object by detected object's id.
|
21
|
+
#
|
22
|
+
# By default the detected object's attributes hash is literally assigned to a
|
23
|
+
# soon-to-be-saved object. If there is a need for more sophisticated behavior,
|
24
|
+
# simply override +merge_attributes_on_import+ method.
|
25
|
+
module ActiveRecordAdapter
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def included(base)
|
29
|
+
base.send(:include, InstanceMethods)
|
30
|
+
base.send(:extend, ClassMethods)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
# The import process is wrapped in a transaction, so if anything goes wrong there is no
|
36
|
+
# harm done.
|
37
|
+
# * +file+ - path to an XML or CVS file, you can also import from other data formats,
|
38
|
+
# but you also need to provide a custom parser to read it
|
39
|
+
# Possible options:
|
40
|
+
# * +parser+ - by default the parser is determined from file extension, but you can force
|
41
|
+
# the imported to use another one by passing it's class here
|
42
|
+
# * +import+ - by default importer returns instance of +Import+ class that contains
|
43
|
+
# detailed report of import process, you can implement your own Import class and force
|
44
|
+
# the importer to use it by passing it's class here
|
45
|
+
def import(file, options = {})
|
46
|
+
import = (options[:import] || Importer::Import).new
|
47
|
+
parser = options[:parser] || Importer::Parser.get_klass(file)
|
48
|
+
data = parser.run(file)
|
49
|
+
|
50
|
+
transaction do
|
51
|
+
data.each do |attributes|
|
52
|
+
imported_object = import.build_imported_object
|
53
|
+
|
54
|
+
if object = find_on_import(import, attributes)
|
55
|
+
imported_object.state = "existing_object"
|
56
|
+
else
|
57
|
+
object = new
|
58
|
+
imported_object.state = "new_object"
|
59
|
+
end
|
60
|
+
|
61
|
+
imported_object.data = attributes
|
62
|
+
object.merge_attributes_on_import(import, attributes)
|
63
|
+
|
64
|
+
unless object.save
|
65
|
+
imported_object.state = "invalid_object"
|
66
|
+
imported_object.validation_errors = object.errors.full_messages
|
67
|
+
end
|
68
|
+
|
69
|
+
imported_object.object = object
|
70
|
+
|
71
|
+
import.add_object(imported_object)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
import
|
76
|
+
end
|
77
|
+
|
78
|
+
# Determines whether a detected object already exists in database.
|
79
|
+
# By default it tries to find an existing objects by id of the detected one.
|
80
|
+
# Returns the object or nil if it's not found.
|
81
|
+
# Override this method in your model to change that default behavior.
|
82
|
+
# * +import+ - current import
|
83
|
+
# * +attributes+ - detected object's attributes hash
|
84
|
+
def find_on_import(import, attributes)
|
85
|
+
find_by_id(attributes["id"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module InstanceMethods
|
90
|
+
# Merges attributes of a detected object with current object's ones.
|
91
|
+
# By default it simply assigns detected attributes to the object.
|
92
|
+
# Override this method in your model to provide some more sophisticated
|
93
|
+
# behavior.
|
94
|
+
# * +import+ - current import
|
95
|
+
# * +attributes+ - detected object's attributes hash
|
96
|
+
def merge_attributes_on_import(import, attributes)
|
97
|
+
self.attributes = attributes
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Importer
|
2
|
+
module Adapters
|
3
|
+
# Adapter for MongoMapper models
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# class Product
|
8
|
+
# include MongoMapper::Document
|
9
|
+
# include Importer
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Product.import(path_to_xml_or_csv_file)
|
13
|
+
#
|
14
|
+
# It sends the given file to a parser and then imports detected objects.
|
15
|
+
# Instead of simply inserting all detected objects to database, the importer
|
16
|
+
# tries to determine wheter a detected object already exists. If so, the object
|
17
|
+
# is only updated, otherwise a new object is created.
|
18
|
+
#
|
19
|
+
# To change the way how importer checks for existing objects (or to turn off this
|
20
|
+
# behavior completely) override +find_on_import+ method. The default behavior now
|
21
|
+
# is to try to find existing object by detected object's id.
|
22
|
+
#
|
23
|
+
# By default the detected object's attributes hash is literally assigned to a
|
24
|
+
# soon-to-be-saved object. If there is a need for more sophisticated behavior,
|
25
|
+
# simply override +merge_attributes_on_import+ method.
|
26
|
+
module MongoMapperAdapter
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def included(base)
|
30
|
+
base.send(:include, InstanceMethods)
|
31
|
+
base.send(:extend, ClassMethods)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# Performs actual import
|
37
|
+
#
|
38
|
+
# Note: unlike with ActiveRecord adapter, import process is not wrapped in a transaction
|
39
|
+
# since mongodb does not support them.
|
40
|
+
#
|
41
|
+
# * +file+ - path to an XML or CVS file, you can also import from other data formats,
|
42
|
+
# but you also need to provide a custom parser to read it
|
43
|
+
# Possible options:
|
44
|
+
# * +parser+ - by default the parser is determined from file extension, but you can force
|
45
|
+
# the imported to use another one by passing it's class here
|
46
|
+
# * +import+ - by default importer returns instance of +Import+ class that contains
|
47
|
+
# detailed report of import process, you can implement your own Import class and force
|
48
|
+
# the importer to use it by passing it's class here
|
49
|
+
def import(file, options = {})
|
50
|
+
import = (options[:import] || Importer::Import).new
|
51
|
+
parser = options[:parser] || Importer::Parser.get_klass(file)
|
52
|
+
data = parser.run(file)
|
53
|
+
|
54
|
+
data.each do |attributes|
|
55
|
+
imported_object = import.build_imported_object
|
56
|
+
|
57
|
+
if object = find_on_import(import, attributes)
|
58
|
+
imported_object.state = "existing_object"
|
59
|
+
else
|
60
|
+
object = new
|
61
|
+
imported_object.state = "new_object"
|
62
|
+
end
|
63
|
+
|
64
|
+
imported_object.data = attributes
|
65
|
+
object.merge_attributes_on_import(import, attributes)
|
66
|
+
|
67
|
+
unless object.save
|
68
|
+
imported_object.state = "invalid_object"
|
69
|
+
imported_object.validation_errors = object.errors.full_messages
|
70
|
+
end
|
71
|
+
|
72
|
+
imported_object.object = object
|
73
|
+
|
74
|
+
import.add_object(imported_object)
|
75
|
+
end
|
76
|
+
|
77
|
+
import
|
78
|
+
end
|
79
|
+
|
80
|
+
# Determines whether a detected object already exists in database.
|
81
|
+
# By default it tries to find an existing objects by id of the detected one.
|
82
|
+
# Returns the object or nil if it's not found.
|
83
|
+
# Override this method in your model to change that default behavior.
|
84
|
+
# * +import+ - current import
|
85
|
+
# * +attributes+ - detected object's attributes hash
|
86
|
+
def find_on_import(import, attributes)
|
87
|
+
find_by_id(attributes["id"])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module InstanceMethods
|
92
|
+
# Merges attributes of a detected object with current object's ones.
|
93
|
+
# By default it simply assigns detected attributes to the object.
|
94
|
+
# Override this method in your model to provide some more sophisticated
|
95
|
+
# behavior.
|
96
|
+
# * +import+ - current import
|
97
|
+
# * +attributes+ - detected object's attributes hash
|
98
|
+
def merge_attributes_on_import(import, attributes)
|
99
|
+
self.attributes = attributes
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|