Empact-activerecord-import 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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