activerecord-import 0.1.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.
@@ -0,0 +1,2 @@
1
+ require File.join File.dirname(__FILE__), "base"
2
+ ActiveRecord::Extensions.require_adapter "mysql"
@@ -0,0 +1,2 @@
1
+ require File.join File.dirname(__FILE__), "base"
2
+ ActiveRecord::Extensions.require_adapter "postgresql"
@@ -0,0 +1,2 @@
1
+ require File.join File.dirname(__FILE__), "base"
2
+ ActiveRecord::Extensions.require_adapter "sqlite3"
@@ -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"] = "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,118 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require "activerecord-import/mysql"
3
+
4
+ describe "#import with :on_duplicate_key_update option (mysql specific functionality)" do
5
+ extend ActiveSupport::TestCase::MySQLAssertions
6
+
7
+ asssertion_group(:should_support_on_duplicate_key_update) do
8
+ should_not_update_fields_not_mentioned
9
+ should_update_foreign_keys
10
+ should_not_update_created_at_on_timestamp_columns
11
+ should_update_updated_at_on_timestamp_columns
12
+ end
13
+
14
+ macro(:perform_import){ raise "supply your own #perform_import in a context below" }
15
+ macro(:updated_topic){ Topic.find(@topic) }
16
+
17
+ context "given columns and values with :validation checks turned off" do
18
+ let(:columns){ %w( id title author_name author_email_address parent_id ) }
19
+ let(:values){ [ [ 99, "Book", "John Doe", "john@doe.com", 17 ] ] }
20
+ let(:updated_values){ [ [ 99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57 ] ] }
21
+
22
+ macro(:perform_import) do |*opts|
23
+ Topic.import columns, updated_values, opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
24
+ end
25
+
26
+ setup do
27
+ Topic.import columns, values, :validate => false
28
+ @topic = Topic.find 99
29
+ end
30
+
31
+ context "using string column names" do
32
+ let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
33
+ should_support_on_duplicate_key_update
34
+ should_update_fields_mentioned
35
+ end
36
+
37
+ context "using symbol column names" do
38
+ let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
39
+ should_support_on_duplicate_key_update
40
+ should_update_fields_mentioned
41
+ end
42
+
43
+ context "using string hash map" do
44
+ let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
45
+ should_support_on_duplicate_key_update
46
+ should_update_fields_mentioned
47
+ end
48
+
49
+ context "using string hash map, but specifying column mismatches" do
50
+ let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
51
+ should_support_on_duplicate_key_update
52
+ should_update_fields_mentioned_with_hash_mappings
53
+ end
54
+
55
+ context "using symbol hash map" do
56
+ let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
57
+ should_support_on_duplicate_key_update
58
+ should_update_fields_mentioned
59
+ end
60
+
61
+ context "using symbol hash map, but specifying column mismatches" do
62
+ let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
63
+ should_support_on_duplicate_key_update
64
+ should_update_fields_mentioned_with_hash_mappings
65
+ end
66
+ end
67
+
68
+ context "given array of model instances with :validation checks turned off" do
69
+ macro(:perform_import) do |*opts|
70
+ @topic.title = "Book - 2nd Edition"
71
+ @topic.author_name = "Author Should Not Change"
72
+ @topic.author_email_address = "johndoe@example.com"
73
+ @topic.parent_id = 57
74
+ Topic.import [@topic], opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
75
+ end
76
+
77
+ setup do
78
+ @topic = Generate(:topic, :id => 99, :author_name => "John Doe", :parent_id => 17)
79
+ end
80
+
81
+ context "using string column names" do
82
+ let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
83
+ should_support_on_duplicate_key_update
84
+ should_update_fields_mentioned
85
+ end
86
+
87
+ context "using symbol column names" do
88
+ let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
89
+ should_support_on_duplicate_key_update
90
+ should_update_fields_mentioned
91
+ end
92
+
93
+ context "using string hash map" do
94
+ let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
95
+ should_support_on_duplicate_key_update
96
+ should_update_fields_mentioned
97
+ end
98
+
99
+ context "using string hash map, but specifying column mismatches" do
100
+ let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
101
+ should_support_on_duplicate_key_update
102
+ should_update_fields_mentioned_with_hash_mappings
103
+ end
104
+
105
+ context "using symbol hash map" do
106
+ let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
107
+ should_support_on_duplicate_key_update
108
+ should_update_fields_mentioned
109
+ end
110
+
111
+ context "using symbol hash map, but specifying column mismatches" do
112
+ let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
113
+ should_support_on_duplicate_key_update
114
+ should_update_fields_mentioned_with_hash_mappings
115
+ end
116
+ end
117
+
118
+ 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