Empact-activerecord-import 0.3.1 → 0.3.2

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.
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 }