Empact-activerecord-import 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -41,7 +41,9 @@ ADAPTERS.each do |adapter|
41
41
  namespace :test do
42
42
  desc "Runs #{adapter} database tests."
43
43
  Rake::TestTask.new(adapter) do |t|
44
- t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/#{adapter}/**/*_test.rb"]
44
+ # FactoryGirl has an issue with warnings, so turn off, so noisy
45
+ # t.warning = true
46
+ t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/active_record/*_test.rb", "test/#{adapter}/**/*_test.rb"]
45
47
  end
46
48
  task adapter
47
49
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
@@ -3,12 +3,6 @@ module ActiveRecord::Import::AbstractAdapter
3
3
  QUERY_OVERHEAD = 8 #This was shown to be true for MySQL, but it's not clear where the overhead is from.
4
4
 
5
5
  module ClassMethods
6
- # Returns the sum of the sizes of the passed in objects. This should
7
- # probably be moved outside this class, but to where?
8
- def sum_sizes( *objects ) # :nodoc:
9
- objects.inject( 0 ){|sum,o| sum += o.size }
10
- end
11
-
12
6
  def get_insert_value_sets( values, sql_size, max_bytes ) # :nodoc:
13
7
  value_sets = []
14
8
  arr, current_arr_values_size, current_size = [], 0, 0
@@ -51,7 +45,7 @@ module ActiveRecord::Import::AbstractAdapter
51
45
  sql_size = QUERY_OVERHEAD + base_sql.size + post_sql.size
52
46
 
53
47
  # the number of bytes the requested insert statement values will take up
54
- values_in_bytes = self.class.sum_sizes( *values )
48
+ values_in_bytes = values.sum {|value| value.size }
55
49
 
56
50
  # the number of bytes (commas) it will take to comma separate our values
57
51
  comma_separated_bytes = values.size-1
@@ -6,7 +6,16 @@ module ActiveRecord::Import::MysqlAdapter
6
6
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
7
7
  end
8
8
  end
9
-
9
+
10
+ # Returns the maximum number of bytes that the server will allow
11
+ # in a single packet
12
+ def max_allowed_packet # :nodoc:
13
+ result = execute( "SHOW VARIABLES like 'max_allowed_packet';" )
14
+ # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
15
+ val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
16
+ val.to_i
17
+ end
18
+
10
19
  # Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
11
20
  # in +args+.
12
21
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
@@ -25,3 +25,4 @@ end
25
25
  this_dir = Pathname.new File.dirname(__FILE__)
26
26
  require this_dir.join("import")
27
27
  require this_dir.join("active_record/adapters/abstract_adapter")
28
+ require this_dir.join("synchronize")
@@ -129,6 +129,11 @@ class ActiveRecord::Base
129
129
  # BlogPost.import posts, :synchronize=>[ post ]
130
130
  # puts post.author_name # => 'yoda'
131
131
  #
132
+ # # Example synchronizing unsaved/new instances in memory by using a uniqued imported field
133
+ # posts = [BlogPost.new(:title => "Foo"), BlogPost.new(:title => "Bar")]
134
+ # BlogPost.import posts, :synchronize => posts
135
+ # puts posts.first.new_record? # => false
136
+ #
132
137
  # == On Duplicate Key Update (MySQL only)
133
138
  #
134
139
  # The :on_duplicate_key_update option can be either an Array or a Hash.
@@ -178,6 +183,9 @@ class ActiveRecord::Base
178
183
  end
179
184
  # end
180
185
  end
186
+ # supports empty array
187
+ elsif args.last.is_a?( Array ) and args.last.empty?
188
+ return ActiveRecord::Import::Result.new([], 0) if args.last.empty?
181
189
  # supports 2-element array and array
182
190
  elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
183
191
  column_names, array_of_attributes = args
@@ -209,7 +217,8 @@ class ActiveRecord::Base
209
217
  end
210
218
 
211
219
  if options[:synchronize]
212
- synchronize( options[:synchronize] )
220
+ sync_keys = options[:synchronize_keys] || [self.primary_key]
221
+ synchronize( options[:synchronize], sync_keys)
213
222
  end
214
223
 
215
224
  return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
@@ -236,7 +245,9 @@ class ActiveRecord::Base
236
245
  # keep track of the instance and the position it is currently at. if this fails
237
246
  # validation we'll use the index to remove it from the array_of_attributes
238
247
  arr.each_with_index do |hsh,i|
239
- instance = new( hsh )
248
+ instance = new do |model|
249
+ hsh.each_pair{ |k,v| model.send("#{k}=", v) }
250
+ end
240
251
  if not instance.valid?
241
252
  array_of_attributes[ i ] = nil
242
253
  failed_instances << instance
@@ -255,9 +266,11 @@ class ActiveRecord::Base
255
266
  # information on +column_names+, +array_of_attributes_ and
256
267
  # +options+.
257
268
  def import_without_validations_or_callbacks( column_names, array_of_attributes, options={} )
269
+ columns = column_names.map { |name| columns_hash[name.to_s] }
270
+
258
271
  columns_sql = "(#{column_names.map{|name| connection.quote_column_name(name) }.join(',')})"
259
272
  insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{quoted_table_name} #{columns_sql} VALUES "
260
- values_sql = values_sql_for_column_names_and_attributes(column_names, array_of_attributes)
273
+ values_sql = values_sql_for_columns_and_attributes(columns, array_of_attributes)
261
274
  if not supports_import?
262
275
  number_inserted = 0
263
276
  values_sql.each do |values|
@@ -280,15 +293,14 @@ class ActiveRecord::Base
280
293
 
281
294
  # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
282
295
  # and +array_of_attributes+.
283
- def values_sql_for_column_names_and_attributes(column_names, array_of_attributes) # :nodoc:
284
- columns = column_names.map { |name| columns_hash[name] }
285
-
296
+ def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
286
297
  array_of_attributes.map do |arr|
287
298
  my_values = arr.each_with_index.map do |val,j|
288
- if !sequence_name.blank? && column_names[j] == primary_key && val.nil?
299
+ column = columns[j]
300
+ if !sequence_name.blank? && column.name == primary_key && val.nil?
289
301
  connection.next_value_for_sequence(sequence_name)
290
302
  else
291
- connection.quote(val, columns[j])
303
+ connection.quote(column.type_cast(val), column)
292
304
  end
293
305
  end
294
306
  "(#{my_values.join(',')})"
@@ -0,0 +1,55 @@
1
+ module ActiveRecord # :nodoc:
2
+ class Base # :nodoc:
3
+
4
+ # Synchronizes the passed in ActiveRecord instances with data
5
+ # from the database. This is like calling reload on an individual
6
+ # ActiveRecord instance but it is intended for use on multiple instances.
7
+ #
8
+ # This uses one query for all instance updates and then updates existing
9
+ # instances rather sending one query for each instance
10
+ #
11
+ # == Examples
12
+ # # Synchronizing existing models by matching on the primary key field
13
+ # posts = Post.find_by_author("Zach")
14
+ # <.. out of system changes occur to change author name from Zach to Zachary..>
15
+ # Post.synchronize posts
16
+ # posts.first.author # => "Zachary" instead of Zach
17
+ #
18
+ # # Synchronizing using custom key fields
19
+ # posts = Post.find_by_author("Zach")
20
+ # <.. out of system changes occur to change the address of author 'Zach' to 1245 Foo Ln ..>
21
+ # Post.synchronize posts, [:name] # queries on the :name column and not the :id column
22
+ # posts.first.address # => "1245 Foo Ln" instead of whatever it was
23
+ #
24
+ def self.synchronize(instances, keys=[self.primary_key])
25
+ return if instances.empty?
26
+
27
+ conditions = {}
28
+ order = ""
29
+
30
+ key_values = keys.map { |key| instances.map(&"#{key}".to_sym) }
31
+ keys.zip(key_values).each { |key, values| conditions[key] = values }
32
+ order = keys.map{ |key| "#{key} ASC" }.join(",")
33
+
34
+ klass = instances.first.class
35
+
36
+ fresh_instances = klass.find( :all, :conditions=>conditions, :order=>order )
37
+ instances.each do |instance|
38
+ matched_instance = fresh_instances.detect do |fresh_instance|
39
+ keys.all?{ |key| fresh_instance.send(key) == instance.send(key) }
40
+ end
41
+
42
+ if matched_instance
43
+ instance.clear_aggregation_cache
44
+ instance.clear_association_cache
45
+ instance.instance_variable_set '@attributes', matched_instance.attributes
46
+ end
47
+ end
48
+ end
49
+
50
+ # See ActiveRecord::ConnectionAdapters::AbstractAdapter.synchronize
51
+ def synchronize(instances, key=[ActiveRecord::Base.primary_key])
52
+ self.class.synchronize(instances, key)
53
+ end
54
+ end
55
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Empact-activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 3
8
- - 1
9
- version: 0.3.1
4
+ prerelease:
5
+ version: 0.3.2
10
6
  platform: ruby
11
7
  authors:
12
8
  - Zach Dennis
@@ -15,8 +11,7 @@ autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
13
 
18
- date: 2010-11-17 00:00:00 -08:00
19
- default_executable:
14
+ date: 2011-05-05 00:00:00 Z
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
22
17
  name: activerecord
@@ -25,44 +20,43 @@ dependencies:
25
20
  requirements:
26
21
  - - ~>
27
22
  - !ruby/object:Gem::Version
28
- segments:
29
- - 3
30
- - 0
31
- - 0
32
23
  version: 3.0.0
33
24
  type: :runtime
34
25
  prerelease: false
35
26
  version_requirements: *id001
36
27
  - !ruby/object:Gem::Dependency
37
- name: jeweler
28
+ name: rake
38
29
  requirement: &id002 !ruby/object:Gem::Requirement
39
30
  none: false
40
31
  requirements:
41
32
  - - ">="
42
33
  - !ruby/object:Gem::Version
43
- segments:
44
- - 1
45
- - 4
46
- - 0
47
- version: 1.4.0
34
+ version: "0"
48
35
  type: :development
49
36
  prerelease: false
50
37
  version_requirements: *id002
51
38
  - !ruby/object:Gem::Dependency
52
- name: activerecord
39
+ name: jeweler
53
40
  requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 1.4.0
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: activerecord
51
+ requirement: &id004 !ruby/object:Gem::Requirement
54
52
  none: false
55
53
  requirements:
56
54
  - - ~>
57
55
  - !ruby/object:Gem::Version
58
- segments:
59
- - 3
60
- - 0
61
- - 0
62
56
  version: 3.0.0
63
57
  type: :runtime
64
58
  prerelease: false
65
- version_requirements: *id003
59
+ version_requirements: *id004
66
60
  description: Extraction of the ActiveRecord::Base#import functionality from ar-extensions for Rails 3 and beyond
67
61
  email: ben.woosley@gmail.com
68
62
  executables: []
@@ -91,28 +85,7 @@ files:
91
85
  - lib/activerecord-import/mysql2.rb
92
86
  - lib/activerecord-import/postgresql.rb
93
87
  - 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
88
+ - lib/activerecord-import/synchronize.rb
116
89
  homepage: http://github.com/Empact/activerecord-import
117
90
  licenses: []
118
91
 
@@ -126,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
99
  requirements:
127
100
  - - ">="
128
101
  - !ruby/object:Gem::Version
129
- hash: -4502227035676277865
102
+ hash: -2706600043388041365
130
103
  segments:
131
104
  - 0
132
105
  version: "0"
@@ -135,35 +108,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
108
  requirements:
136
109
  - - ">="
137
110
  - !ruby/object:Gem::Version
138
- segments:
139
- - 0
140
111
  version: "0"
141
112
  requirements: []
142
113
 
143
114
  rubyforge_project:
144
- rubygems_version: 1.3.7
115
+ rubygems_version: 1.7.2
145
116
  signing_key:
146
117
  specification_version: 3
147
118
  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
119
+ test_files: []
120
+
@@ -1,52 +0,0 @@
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
@@ -1 +0,0 @@
1
- ENV["ARE_DB"] = "mysql"
@@ -1 +0,0 @@
1
- ENV["ARE_DB"] = "mysql2"
@@ -1 +0,0 @@
1
- ENV["ARE_DB"] = "postgresql"
@@ -1 +0,0 @@
1
- ENV["ARE_DB"] = "sqlite3"
data/test/import_test.rb DELETED
@@ -1,202 +0,0 @@
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
data/test/models/book.rb DELETED
@@ -1,3 +0,0 @@
1
- class Book < ActiveRecord::Base
2
- belongs_to :topic
3
- end
data/test/models/group.rb DELETED
@@ -1,3 +0,0 @@
1
- class Group < ActiveRecord::Base
2
- self.table_name = 'group'
3
- end
data/test/models/topic.rb DELETED
@@ -1,7 +0,0 @@
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
@@ -1,6 +0,0 @@
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
@@ -1,6 +0,0 @@
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
@@ -1,20 +0,0 @@
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
@@ -1,98 +0,0 @@
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
@@ -1,17 +0,0 @@
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
@@ -1,4 +0,0 @@
1
- class SchemaInfo < ActiveRecord::Base
2
- set_table_name 'schema_info'
3
- VERSION = 12
4
- end
@@ -1,67 +0,0 @@
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
-
@@ -1,13 +0,0 @@
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
@@ -1,29 +0,0 @@
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
@@ -1,55 +0,0 @@
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
@@ -1,117 +0,0 @@
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
data/test/test_helper.rb DELETED
@@ -1,46 +0,0 @@
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 }