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,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ describe "ActiveRecord::ConnectionAdapter::AbstractAdapter" do
4
+ context "#get_insert_value_sets - computing insert value sets" do
5
+ let(:adapter){ ActiveRecord::ConnectionAdapters::AbstractAdapter }
6
+ let(:base_sql){ "INSERT INTO atable (a,b,c)" }
7
+ let(:values){ [ "(1,2,3)", "(2,3,4)", "(3,4,5)" ] }
8
+
9
+ context "when the max allowed bytes is 33 and the base SQL is 26 bytes" do
10
+ it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
11
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 33
12
+ assert_equal 3, value_sets.size
13
+ end
14
+ end
15
+
16
+ context "when the max allowed bytes is 40 and the base SQL is 26 bytes" do
17
+ it "should return 3 value sets when given 3 value sets of 7 bytes a piece" do
18
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 40
19
+ assert_equal 3, value_sets.size
20
+ end
21
+ end
22
+
23
+ context "when the max allowed bytes is 41 and the base SQL is 26 bytes" do
24
+ it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
25
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 41
26
+ assert_equal 2, value_sets.size
27
+ end
28
+ end
29
+
30
+ context "when the max allowed bytes is 48 and the base SQL is 26 bytes" do
31
+ it "should return 2 value sets when given 2 value sets of 7 bytes a piece" do
32
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 48
33
+ assert_equal 2, value_sets.size
34
+ end
35
+ end
36
+
37
+ context "when the max allowed bytes is 49 and the base SQL is 26 bytes" do
38
+ it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
39
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 49
40
+ assert_equal 1, value_sets.size
41
+ end
42
+ end
43
+
44
+ context "when the max allowed bytes is 999999 and the base SQL is 26 bytes" do
45
+ it "should return 1 value sets when given 1 value sets of 7 bytes a piece" do
46
+ value_sets = adapter.get_insert_value_sets values, base_sql.size, max_allowed_bytes = 999999
47
+ assert_equal 1, value_sets.size
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "mysql"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "mysql2"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "postgresql"
@@ -0,0 +1 @@
1
+ ENV["ARE_DB"] = "sqlite3"
@@ -0,0 +1,202 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "#import" do
4
+ it "should return the number of inserts performed" do
5
+ # see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
6
+ assert_difference "Topic.count", +10 do
7
+ result = Topic.import Build(3, :topics)
8
+ assert result.num_inserts > 0
9
+
10
+ result = Topic.import Build(7, :topics)
11
+ assert result.num_inserts > 0
12
+ end
13
+ end
14
+
15
+ context "with :validation option" do
16
+ let(:columns) { %w(title author_name) }
17
+ let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
18
+ let(:invalid_values) { [[ "The RSpec Book", ""], ["Agile+UX", ""]] }
19
+
20
+ context "with validation checks turned off" do
21
+ it "should import valid data" do
22
+ assert_difference "Topic.count", +2 do
23
+ result = Topic.import columns, valid_values, :validate => false
24
+ end
25
+ end
26
+
27
+ it "should import invalid data" do
28
+ assert_difference "Topic.count", +2 do
29
+ result = Topic.import columns, invalid_values, :validate => false
30
+ end
31
+ end
32
+ end
33
+
34
+ context "with validation checks turned on" do
35
+ it "should import valid data" do
36
+ assert_difference "Topic.count", +2 do
37
+ result = Topic.import columns, valid_values, :validate => true
38
+ end
39
+ end
40
+
41
+ it "should not import invalid data" do
42
+ assert_no_difference "Topic.count" do
43
+ result = Topic.import columns, invalid_values, :validate => true
44
+ end
45
+ end
46
+
47
+ it "should report the failed instances" do
48
+ results = Topic.import columns, invalid_values, :validate => true
49
+ assert_equal invalid_values.size, results.failed_instances.size
50
+ results.failed_instances.each{ |e| assert_kind_of Topic, e }
51
+ end
52
+
53
+ it "should import valid data when mixed with invalid data" do
54
+ assert_difference "Topic.count", +2 do
55
+ result = Topic.import columns, valid_values + invalid_values, :validate => true
56
+ end
57
+ assert_equal 0, Topic.find_all_by_title(invalid_values.map(&:first)).count
58
+ end
59
+ end
60
+ end
61
+
62
+ context "with an array of unsaved model instances" do
63
+ let(:topic) { Build(:topic, :title => "The RSpec Book", :author_name => "David Chelimsky")}
64
+ let(:topics) { Build(9, :topics) }
65
+ let(:invalid_topics){ Build(7, :invalid_topics)}
66
+
67
+ it "should import records based on those model's attributes" do
68
+ assert_difference "Topic.count", +9 do
69
+ result = Topic.import topics
70
+ end
71
+
72
+ Topic.import [topic]
73
+ assert Topic.find_by_title_and_author_name("The RSpec Book", "David Chelimsky")
74
+ end
75
+
76
+ it "should not overwrite existing records" do
77
+ topic = Generate(:topic, :title => "foobar")
78
+ assert_no_difference "Topic.count" do
79
+ begin
80
+ Topic.transaction do
81
+ topic.title = "baz"
82
+ Topic.import [topic]
83
+ end
84
+ rescue Exception
85
+ # PostgreSQL raises PgError due to key constraints
86
+ # I don't know why ActiveRecord doesn't catch these. *sigh*
87
+ end
88
+ end
89
+ assert_equal "foobar", topic.reload.title
90
+ end
91
+
92
+ context "with validation checks turned on" do
93
+ it "should import valid models" do
94
+ assert_difference "Topic.count", +9 do
95
+ result = Topic.import topics, :validate => true
96
+ end
97
+ end
98
+
99
+ it "should not import invalid models" do
100
+ assert_no_difference "Topic.count" do
101
+ result = Topic.import invalid_topics, :validate => true
102
+ end
103
+ end
104
+ end
105
+
106
+ context "with validation checks turned off" do
107
+ it "should import invalid models" do
108
+ assert_difference "Topic.count", +7 do
109
+ result = Topic.import invalid_topics, :validate => false
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ context "with an array of columns and an array of unsaved model instances" do
116
+ let(:topics) { Build(2, :topics) }
117
+
118
+ it "should import records populating the supplied columns with the corresponding model instance attributes" do
119
+ assert_difference "Topic.count", +2 do
120
+ result = Topic.import [:author_name, :title], topics
121
+ end
122
+
123
+ # imported topics should be findable by their imported attributes
124
+ assert Topic.find_by_author_name(topics.first.author_name)
125
+ assert Topic.find_by_author_name(topics.last.author_name)
126
+ end
127
+
128
+ it "should not populate fields for columns not imported" do
129
+ topics.first.author_email_address = "zach.dennis@gmail.com"
130
+ assert_difference "Topic.count", +2 do
131
+ result = Topic.import [:author_name, :title], topics
132
+ end
133
+
134
+ assert !Topic.find_by_author_email_address("zach.dennis@gmail.com")
135
+ end
136
+ end
137
+
138
+ context "ActiveRecord timestamps" do
139
+ context "when the timestamps columns are present" do
140
+ setup do
141
+ Delorean.time_travel_to("5 minutes ago") do
142
+ assert_difference "Book.count", +1 do
143
+ result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
144
+ end
145
+ end
146
+ @book = Book.last
147
+ end
148
+
149
+ it "should set the created_at column for new records" do
150
+ assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
151
+ end
152
+
153
+ it "should set the created_on column for new records" do
154
+ assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
155
+ end
156
+
157
+ it "should set the updated_at column for new records" do
158
+ assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
159
+ end
160
+
161
+ it "should set the updated_on column for new records" do
162
+ assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
163
+ end
164
+ end
165
+
166
+ context "when a custom time zone is set" do
167
+ setup do
168
+ original_timezone = ActiveRecord::Base.default_timezone
169
+ ActiveRecord::Base.default_timezone = :utc
170
+ Delorean.time_travel_to("5 minutes ago") do
171
+ assert_difference "Book.count", +1 do
172
+ result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
173
+ end
174
+ end
175
+ ActiveRecord::Base.default_timezone = original_timezone
176
+ @book = Book.last
177
+ end
178
+
179
+ it "should set the created_at and created_on timestamps for new records" do
180
+ assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
181
+ assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
182
+ end
183
+
184
+ it "should set the updated_at and updated_on timestamps for new records" do
185
+ assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
186
+ assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
187
+ end
188
+ end
189
+ end
190
+
191
+ context "importing with database reserved words" do
192
+ let(:group) { Build(:group, :order => "superx") }
193
+
194
+ it "should import just fine" do
195
+ assert_difference "Group.count", +1 do
196
+ result = Group.import [group]
197
+ end
198
+ assert_equal "superx", Group.first.order
199
+ end
200
+ end
201
+
202
+ end
@@ -0,0 +1,3 @@
1
+ class Book < ActiveRecord::Base
2
+ belongs_to :topic
3
+ end
@@ -0,0 +1,3 @@
1
+ class Group < ActiveRecord::Base
2
+ self.table_name = 'group'
3
+ end
@@ -0,0 +1,7 @@
1
+ class Topic < ActiveRecord::Base
2
+ validates_presence_of :author_name
3
+ has_many :books
4
+ belongs_to :parent, :class_name => "Topic"
5
+
6
+ composed_of :description, :mapping => [ %w(title title), %w(author_name author_name)], :allow_nil => true, :class_name => "TopicDescription"
7
+ end
@@ -0,0 +1,6 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+
6
+ should_support_mysql_import_functionality
@@ -0,0 +1,6 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
5
+
6
+ should_support_mysql_import_functionality
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe "#supports_imports?" do
4
+ it "should support import" do
5
+ assert ActiveRecord::Base.supports_import?
6
+ end
7
+ end
8
+
9
+ describe "#import" do
10
+ it "should import with a single insert" do
11
+ # see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
12
+ assert_difference "Topic.count", +10 do
13
+ result = Topic.import Build(3, :topics)
14
+ assert_equal 1, result.num_inserts
15
+
16
+ result = Topic.import Build(7, :topics)
17
+ assert_equal 1, result.num_inserts
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :schema_info, :force=>true do |t|
4
+ t.column :version, :integer, :unique=>true
5
+ end
6
+ SchemaInfo.create :version=>SchemaInfo::VERSION
7
+
8
+ create_table :group, :force => true do |t|
9
+ t.column :order, :string
10
+ t.timestamps
11
+ end
12
+
13
+ create_table :topics, :force=>true do |t|
14
+ t.column :title, :string, :null => false
15
+ t.column :author_name, :string
16
+ t.column :author_email_address, :string
17
+ t.column :written_on, :datetime
18
+ t.column :bonus_time, :time
19
+ t.column :last_read, :datetime
20
+ t.column :content, :text
21
+ t.column :approved, :boolean, :default=>'1'
22
+ t.column :replies_count, :integer
23
+ t.column :parent_id, :integer
24
+ t.column :type, :string
25
+ t.column :created_at, :datetime
26
+ t.column :created_on, :datetime
27
+ t.column :updated_at, :datetime
28
+ t.column :updated_on, :datetime
29
+ end
30
+
31
+ create_table :projects, :force=>true do |t|
32
+ t.column :name, :string
33
+ t.column :type, :string
34
+ end
35
+
36
+ create_table :developers, :force=>true do |t|
37
+ t.column :name, :string
38
+ t.column :salary, :integer, :default=>'70000'
39
+ t.column :created_at, :datetime
40
+ t.column :team_id, :integer
41
+ t.column :updated_at, :datetime
42
+ end
43
+
44
+ create_table :addresses, :force=>true do |t|
45
+ t.column :address, :string
46
+ t.column :city, :string
47
+ t.column :state, :string
48
+ t.column :zip, :string
49
+ t.column :developer_id, :integer
50
+ end
51
+
52
+ create_table :teams, :force=>true do |t|
53
+ t.column :name, :string
54
+ end
55
+
56
+ create_table :books, :force=>true do |t|
57
+ t.column :title, :string, :null=>false
58
+ t.column :publisher, :string, :null=>false, :default => 'Default Publisher'
59
+ t.column :author_name, :string, :null=>false
60
+ t.column :created_at, :datetime
61
+ t.column :created_on, :datetime
62
+ t.column :updated_at, :datetime
63
+ t.column :updated_on, :datetime
64
+ t.column :publish_date, :date
65
+ t.column :topic_id, :integer
66
+ t.column :for_sale, :boolean, :default => true
67
+ end
68
+
69
+ create_table :languages, :force=>true do |t|
70
+ t.column :name, :string
71
+ t.column :developer_id, :integer
72
+ end
73
+
74
+ create_table :shopping_carts, :force=>true do |t|
75
+ t.column :name, :string, :null => true
76
+ t.column :created_at, :datetime
77
+ t.column :updated_at, :datetime
78
+ end
79
+
80
+ create_table :cart_items, :force => true do |t|
81
+ t.column :shopping_cart_id, :string, :null => false
82
+ t.column :book_id, :string, :null => false
83
+ t.column :copies, :integer, :default => 1
84
+ t.column :created_at, :datetime
85
+ t.column :updated_at, :datetime
86
+ end
87
+
88
+ add_index :cart_items, [:shopping_cart_id, :book_id], :unique => true, :name => 'uk_shopping_cart_books'
89
+
90
+ create_table :animals, :force => true do |t|
91
+ t.column :name, :string, :null => false
92
+ t.column :size, :string, :default => nil
93
+ t.column :created_at, :datetime
94
+ t.column :updated_at, :datetime
95
+ end
96
+
97
+ add_index :animals, [:name], :unique => true, :name => 'uk_animals'
98
+ end