Empact-activerecord-import 0.3.0

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.
Files changed (41) hide show
  1. data/README.markdown +25 -0
  2. data/Rakefile +72 -0
  3. data/VERSION +1 -0
  4. data/lib/activerecord-import.rb +16 -0
  5. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +10 -0
  6. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
  7. data/lib/activerecord-import/active_record/adapters/mysql_adapter.rb +6 -0
  8. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +7 -0
  9. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +7 -0
  10. data/lib/activerecord-import/adapters/abstract_adapter.rb +125 -0
  11. data/lib/activerecord-import/adapters/mysql_adapter.rb +50 -0
  12. data/lib/activerecord-import/adapters/postgresql_adapter.rb +13 -0
  13. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +7 -0
  14. data/lib/activerecord-import/base.rb +27 -0
  15. data/lib/activerecord-import/import.rb +347 -0
  16. data/lib/activerecord-import/mysql.rb +8 -0
  17. data/lib/activerecord-import/mysql2.rb +8 -0
  18. data/lib/activerecord-import/postgresql.rb +8 -0
  19. data/lib/activerecord-import/sqlite3.rb +8 -0
  20. data/test/active_record/connection_adapter_test.rb +52 -0
  21. data/test/adapters/mysql.rb +1 -0
  22. data/test/adapters/mysql2.rb +1 -0
  23. data/test/adapters/postgresql.rb +1 -0
  24. data/test/adapters/sqlite3.rb +1 -0
  25. data/test/import_test.rb +202 -0
  26. data/test/models/book.rb +3 -0
  27. data/test/models/group.rb +3 -0
  28. data/test/models/topic.rb +7 -0
  29. data/test/mysql/import_test.rb +6 -0
  30. data/test/mysql2/import_test.rb +6 -0
  31. data/test/postgresql/import_test.rb +20 -0
  32. data/test/schema/generic_schema.rb +98 -0
  33. data/test/schema/mysql_schema.rb +17 -0
  34. data/test/schema/version.rb +4 -0
  35. data/test/support/active_support/test_case_extensions.rb +67 -0
  36. data/test/support/factories.rb +13 -0
  37. data/test/support/generate.rb +29 -0
  38. data/test/support/mysql/assertions.rb +55 -0
  39. data/test/support/mysql/import_examples.rb +117 -0
  40. data/test/test_helper.rb +46 -0
  41. metadata +169 -0
@@ -0,0 +1,17 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :books, :options=>'ENGINE=MyISAM', :force=>true do |t|
4
+ t.column :title, :string, :null=>false
5
+ t.column :publisher, :string, :null=>false, :default => 'Default Publisher'
6
+ t.column :author_name, :string, :null=>false
7
+ t.column :created_at, :datetime
8
+ t.column :created_on, :datetime
9
+ t.column :updated_at, :datetime
10
+ t.column :updated_on, :datetime
11
+ t.column :publish_date, :date
12
+ t.column :topic_id, :integer
13
+ t.column :for_sale, :boolean, :default => true
14
+ end
15
+ execute "ALTER TABLE books ADD FULLTEXT( `title`, `publisher`, `author_name` )"
16
+
17
+ end
@@ -0,0 +1,4 @@
1
+ class SchemaInfo < ActiveRecord::Base
2
+ set_table_name 'schema_info'
3
+ VERSION = 12
4
+ end
@@ -0,0 +1,67 @@
1
+ class ActiveSupport::TestCase
2
+ include ActiveRecord::TestFixtures
3
+ self.use_transactional_fixtures = true
4
+
5
+ class << self
6
+ def assertion(name, &block)
7
+ mc = class << self ; self ; end
8
+ mc.class_eval do
9
+ define_method(name) do
10
+ it(name, &block)
11
+ end
12
+ end
13
+ end
14
+
15
+ def asssertion_group(name, &block)
16
+ mc = class << self ; self ; end
17
+ mc.class_eval do
18
+ define_method(name, &block)
19
+ end
20
+ end
21
+
22
+ def macro(name, &block)
23
+ class_eval do
24
+ define_method(name, &block)
25
+ end
26
+ end
27
+
28
+ def describe(description, toplevel=nil, &blk)
29
+ text = toplevel ? description : "#{name} #{description}"
30
+ klass = Class.new(self)
31
+
32
+ klass.class_eval <<-RUBY_EVAL
33
+ def self.name
34
+ "#{text}"
35
+ end
36
+ RUBY_EVAL
37
+
38
+ # do not inherit test methods from the superclass
39
+ klass.class_eval do
40
+ instance_methods.grep(/^test.+/) do |method|
41
+ undef_method method
42
+ end
43
+ end
44
+
45
+ klass.instance_eval &blk
46
+ end
47
+ alias_method :context, :describe
48
+
49
+ def let(name, &blk)
50
+ values = {}
51
+ define_method(name) do
52
+ return values[name] if values.has_key?(name)
53
+ values[name] = instance_eval(&blk)
54
+ end
55
+ end
56
+
57
+ def it(description, &blk)
58
+ define_method("test: #{name} #{description}", &blk)
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ def describe(description, &blk)
65
+ ActiveSupport::TestCase.describe(description, true, &blk)
66
+ end
67
+
@@ -0,0 +1,13 @@
1
+ Factory.define :group do |m|
2
+ m.sequence(:order) { |n| "Order #{n}" }
3
+ end
4
+
5
+ Factory.define :invalid_topic, :class => "Topic" do |m|
6
+ m.sequence(:title){ |n| "Title #{n}"}
7
+ m.author_name nil
8
+ end
9
+
10
+ Factory.define :topic do |m|
11
+ m.sequence(:title){ |n| "Title #{n}"}
12
+ m.sequence(:author_name){ |n| "Author #{n}"}
13
+ end
@@ -0,0 +1,29 @@
1
+ class ActiveSupport::TestCase
2
+ def Build(*args)
3
+ n = args.shift if args.first.is_a?(Numeric)
4
+ factory = args.shift
5
+ factory_girl_args = args.shift || {}
6
+
7
+ if n
8
+ Array.new.tap do |collection|
9
+ n.times.each { collection << Factory.build(factory.to_s.singularize.to_sym, factory_girl_args) }
10
+ end
11
+ else
12
+ Factory.build(factory.to_s.singularize.to_sym, factory_girl_args)
13
+ end
14
+ end
15
+
16
+ def Generate(*args)
17
+ n = args.shift if args.first.is_a?(Numeric)
18
+ factory = args.shift
19
+ factory_girl_args = args.shift || {}
20
+
21
+ if n
22
+ Array.new.tap do |collection|
23
+ n.times.each { collection << Factory.create(factory.to_s.singularize.to_sym, factory_girl_args) }
24
+ end
25
+ else
26
+ Factory.create(factory.to_s.singularize.to_sym, factory_girl_args)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ class ActiveSupport::TestCase
2
+ module MySQLAssertions
3
+ def self.extended(klass)
4
+ klass.instance_eval do
5
+ assertion(:should_not_update_created_at_on_timestamp_columns) do
6
+ Delorean.time_travel_to("5 minutes from now") do
7
+ perform_import
8
+ assert_equal @topic.created_at.to_i, updated_topic.created_at.to_i
9
+ assert_equal @topic.created_on.to_i, updated_topic.created_on.to_i
10
+ end
11
+ end
12
+
13
+ assertion(:should_update_updated_at_on_timestamp_columns) do
14
+ time = Chronic.parse("5 minutes from now")
15
+ Delorean.time_travel_to(time) do
16
+ perform_import
17
+ assert_equal time.to_i, updated_topic.updated_at.to_i
18
+ assert_equal time.to_i, updated_topic.updated_on.to_i
19
+ end
20
+ end
21
+
22
+ assertion(:should_not_update_timestamps) do
23
+ Delorean.time_travel_to("5 minutes from now") do
24
+ perform_import :timestamps => false
25
+ assert_equal @topic.created_at.to_i, updated_topic.created_at.to_i
26
+ assert_equal @topic.created_on.to_i, updated_topic.created_on.to_i
27
+ assert_equal @topic.updated_at.to_i, updated_topic.updated_at.to_i
28
+ assert_equal @topic.updated_on.to_i, updated_topic.updated_on.to_i
29
+ end
30
+ end
31
+
32
+ assertion(:should_not_update_fields_not_mentioned) do
33
+ assert_equal "John Doe", updated_topic.author_name
34
+ end
35
+
36
+ assertion(:should_update_fields_mentioned) do
37
+ perform_import
38
+ assert_equal "Book - 2nd Edition", updated_topic.title
39
+ assert_equal "johndoe@example.com", updated_topic.author_email_address
40
+ end
41
+
42
+ assertion(:should_update_fields_mentioned_with_hash_mappings) do
43
+ perform_import
44
+ assert_equal "johndoe@example.com", updated_topic.title
45
+ assert_equal "Book - 2nd Edition", updated_topic.author_email_address
46
+ end
47
+
48
+ assertion(:should_update_foreign_keys) do
49
+ perform_import
50
+ assert_equal 57, updated_topic.parent_id
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,117 @@
1
+ def should_support_mysql_import_functionality
2
+ describe "#import with :on_duplicate_key_update option (mysql specific functionality)" do
3
+ extend ActiveSupport::TestCase::MySQLAssertions
4
+
5
+ asssertion_group(:should_support_on_duplicate_key_update) do
6
+ should_not_update_fields_not_mentioned
7
+ should_update_foreign_keys
8
+ should_not_update_created_at_on_timestamp_columns
9
+ should_update_updated_at_on_timestamp_columns
10
+ end
11
+
12
+ macro(:perform_import){ raise "supply your own #perform_import in a context below" }
13
+ macro(:updated_topic){ Topic.find(@topic) }
14
+
15
+ context "given columns and values with :validation checks turned off" do
16
+ let(:columns){ %w( id title author_name author_email_address parent_id ) }
17
+ let(:values){ [ [ 99, "Book", "John Doe", "john@doe.com", 17 ] ] }
18
+ let(:updated_values){ [ [ 99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57 ] ] }
19
+
20
+ macro(:perform_import) do |*opts|
21
+ Topic.import columns, updated_values, opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
22
+ end
23
+
24
+ setup do
25
+ Topic.import columns, values, :validate => false
26
+ @topic = Topic.find 99
27
+ end
28
+
29
+ context "using string column names" do
30
+ let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
31
+ should_support_on_duplicate_key_update
32
+ should_update_fields_mentioned
33
+ end
34
+
35
+ context "using symbol column names" do
36
+ let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
37
+ should_support_on_duplicate_key_update
38
+ should_update_fields_mentioned
39
+ end
40
+
41
+ context "using string hash map" do
42
+ let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
43
+ should_support_on_duplicate_key_update
44
+ should_update_fields_mentioned
45
+ end
46
+
47
+ context "using string hash map, but specifying column mismatches" do
48
+ let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
49
+ should_support_on_duplicate_key_update
50
+ should_update_fields_mentioned_with_hash_mappings
51
+ end
52
+
53
+ context "using symbol hash map" do
54
+ let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
55
+ should_support_on_duplicate_key_update
56
+ should_update_fields_mentioned
57
+ end
58
+
59
+ context "using symbol hash map, but specifying column mismatches" do
60
+ let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
61
+ should_support_on_duplicate_key_update
62
+ should_update_fields_mentioned_with_hash_mappings
63
+ end
64
+ end
65
+
66
+ context "given array of model instances with :validation checks turned off" do
67
+ macro(:perform_import) do |*opts|
68
+ @topic.title = "Book - 2nd Edition"
69
+ @topic.author_name = "Author Should Not Change"
70
+ @topic.author_email_address = "johndoe@example.com"
71
+ @topic.parent_id = 57
72
+ Topic.import [@topic], opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
73
+ end
74
+
75
+ setup do
76
+ @topic = Generate(:topic, :id => 99, :author_name => "John Doe", :parent_id => 17)
77
+ end
78
+
79
+ context "using string column names" do
80
+ let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
81
+ should_support_on_duplicate_key_update
82
+ should_update_fields_mentioned
83
+ end
84
+
85
+ context "using symbol column names" do
86
+ let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
87
+ should_support_on_duplicate_key_update
88
+ should_update_fields_mentioned
89
+ end
90
+
91
+ context "using string hash map" do
92
+ let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
93
+ should_support_on_duplicate_key_update
94
+ should_update_fields_mentioned
95
+ end
96
+
97
+ context "using string hash map, but specifying column mismatches" do
98
+ let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
99
+ should_support_on_duplicate_key_update
100
+ should_update_fields_mentioned_with_hash_mappings
101
+ end
102
+
103
+ context "using symbol hash map" do
104
+ let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
105
+ should_support_on_duplicate_key_update
106
+ should_update_fields_mentioned
107
+ end
108
+
109
+ context "using symbol hash map, but specifying column mismatches" do
110
+ let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
111
+ should_support_on_duplicate_key_update
112
+ should_update_fields_mentioned_with_hash_mappings
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,46 @@
1
+ require 'pathname'
2
+ test_dir = Pathname.new File.dirname(__FILE__)
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require "fileutils"
7
+ require "rubygems"
8
+
9
+ ENV["RAILS_ENV"] = "test"
10
+
11
+ require "bundler"
12
+ Bundler.setup
13
+
14
+ require "logger"
15
+ require 'test/unit'
16
+ require "active_record"
17
+ require "active_record/fixtures"
18
+ require "active_support/test_case"
19
+
20
+ require "delorean"
21
+ require "ruby-debug"
22
+
23
+ adapter = ENV["ARE_DB"] || "sqlite3"
24
+
25
+ FileUtils.mkdir_p 'log'
26
+ ActiveRecord::Base.logger = Logger.new("log/test.log")
27
+ ActiveRecord::Base.logger.level = Logger::DEBUG
28
+ ActiveRecord::Base.configurations["test"] = YAML.load(test_dir.join("database.yml").open)[adapter]
29
+
30
+ require "activerecord-import"
31
+ ActiveRecord::Base.establish_connection "test"
32
+
33
+ ActiveSupport::Notifications.subscribe(/active_record.sql/) do |event, _, _, _, hsh|
34
+ ActiveRecord::Base.logger.info hsh[:sql]
35
+ end
36
+
37
+ require "factory_girl"
38
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each{ |file| require file }
39
+
40
+ # Load base/generic schema
41
+ require test_dir.join("schema/version")
42
+ require test_dir.join("schema/generic_schema")
43
+ adapter_schema = test_dir.join("schema/#{adapter}_schema.rb")
44
+ require adapter_schema if File.exists?(adapter_schema)
45
+
46
+ Dir[File.dirname(__FILE__) + "/models/*.rb"].each{ |file| require file }
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Empact-activerecord-import
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
+ platform: ruby
11
+ authors:
12
+ - Zach Dennis
13
+ - Ben Woosley
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-17 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 3
30
+ - 0
31
+ - 0
32
+ version: 3.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: jeweler
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 4
46
+ - 0
47
+ version: 1.4.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: activerecord
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 3
60
+ - 0
61
+ - 0
62
+ version: 3.0.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: *id003
66
+ description: Extraction of the ActiveRecord::Base#import functionality from ar-extensions for Rails 3 and beyond
67
+ email: ben.woosley@gmail.com
68
+ executables: []
69
+
70
+ extensions: []
71
+
72
+ extra_rdoc_files:
73
+ - README.markdown
74
+ files:
75
+ - README.markdown
76
+ - Rakefile
77
+ - VERSION
78
+ - lib/activerecord-import.rb
79
+ - lib/activerecord-import/active_record/adapters/abstract_adapter.rb
80
+ - lib/activerecord-import/active_record/adapters/mysql2_adapter.rb
81
+ - lib/activerecord-import/active_record/adapters/mysql_adapter.rb
82
+ - lib/activerecord-import/active_record/adapters/postgresql_adapter.rb
83
+ - lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb
84
+ - lib/activerecord-import/adapters/abstract_adapter.rb
85
+ - lib/activerecord-import/adapters/mysql_adapter.rb
86
+ - lib/activerecord-import/adapters/postgresql_adapter.rb
87
+ - lib/activerecord-import/adapters/sqlite3_adapter.rb
88
+ - lib/activerecord-import/base.rb
89
+ - lib/activerecord-import/import.rb
90
+ - lib/activerecord-import/mysql.rb
91
+ - lib/activerecord-import/mysql2.rb
92
+ - lib/activerecord-import/postgresql.rb
93
+ - lib/activerecord-import/sqlite3.rb
94
+ - test/active_record/connection_adapter_test.rb
95
+ - test/adapters/mysql.rb
96
+ - test/adapters/mysql2.rb
97
+ - test/adapters/postgresql.rb
98
+ - test/adapters/sqlite3.rb
99
+ - test/import_test.rb
100
+ - test/models/book.rb
101
+ - test/models/group.rb
102
+ - test/models/topic.rb
103
+ - test/mysql/import_test.rb
104
+ - test/mysql2/import_test.rb
105
+ - test/postgresql/import_test.rb
106
+ - test/schema/generic_schema.rb
107
+ - test/schema/mysql_schema.rb
108
+ - test/schema/version.rb
109
+ - test/support/active_support/test_case_extensions.rb
110
+ - test/support/factories.rb
111
+ - test/support/generate.rb
112
+ - test/support/mysql/assertions.rb
113
+ - test/support/mysql/import_examples.rb
114
+ - test/test_helper.rb
115
+ has_rdoc: true
116
+ homepage: http://github.com/Empact/activerecord-import
117
+ licenses: []
118
+
119
+ post_install_message:
120
+ rdoc_options: []
121
+
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ hash: 1663017745777293064
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ requirements: []
142
+
143
+ rubyforge_project:
144
+ rubygems_version: 1.3.7
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: Bulk-loading extension for ActiveRecord
148
+ test_files:
149
+ - test/active_record/connection_adapter_test.rb
150
+ - test/adapters/mysql.rb
151
+ - test/adapters/mysql2.rb
152
+ - test/adapters/postgresql.rb
153
+ - test/adapters/sqlite3.rb
154
+ - test/import_test.rb
155
+ - test/models/book.rb
156
+ - test/models/group.rb
157
+ - test/models/topic.rb
158
+ - test/mysql/import_test.rb
159
+ - test/mysql2/import_test.rb
160
+ - test/postgresql/import_test.rb
161
+ - test/schema/generic_schema.rb
162
+ - test/schema/mysql_schema.rb
163
+ - test/schema/version.rb
164
+ - test/support/active_support/test_case_extensions.rb
165
+ - test/support/factories.rb
166
+ - test/support/generate.rb
167
+ - test/support/mysql/assertions.rb
168
+ - test/support/mysql/import_examples.rb
169
+ - test/test_helper.rb