activerecord-import 0.2.5 → 0.2.6

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,6 +41,8 @@ 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
+ # FactoryGirl has an issue with warnings, so turn off, so noisy
45
+ # t.warning = true
44
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
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.5
1
+ 0.2.6
@@ -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?
@@ -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
data/test/import_test.rb CHANGED
@@ -12,6 +12,13 @@ describe "#import" do
12
12
  end
13
13
  end
14
14
 
15
+ it "should not produce an error when importing empty arrays" do
16
+ assert_nothing_raised do
17
+ Topic.import []
18
+ Topic.import %w(title author_name), []
19
+ end
20
+ end
21
+
15
22
  context "with :validation option" do
16
23
  let(:columns) { %w(title author_name) }
17
24
  let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
@@ -59,6 +66,26 @@ describe "#import" do
59
66
  end
60
67
  end
61
68
 
69
+ context "with :synchronize option" do
70
+ context "synchronizing on new records" do
71
+ let(:new_topics) { Build(3, :topics) }
72
+
73
+ it "doesn't reload any data (doesn't work)" do
74
+ Topic.import new_topics, :synchronize => new_topics
75
+ assert new_topics.all?(&:new_record?), "No record should have been reloaded"
76
+ end
77
+ end
78
+
79
+ context "synchronizing on new records with explicit conditions" do
80
+ let(:new_topics) { Build(3, :topics) }
81
+
82
+ it "reloads data for existing in-memory instances" do
83
+ Topic.import(new_topics, :synchronize => new_topics, :synchronize_key => [:title] )
84
+ assert new_topics.all?(&:new_record?), "Records should have been reloaded"
85
+ end
86
+ end
87
+ end
88
+
62
89
  context "with an array of unsaved model instances" do
63
90
  let(:topic) { Build(:topic, :title => "The RSpec Book", :author_name => "David Chelimsky")}
64
91
  let(:topics) { Build(9, :topics) }
@@ -135,6 +162,13 @@ describe "#import" do
135
162
  end
136
163
  end
137
164
 
165
+ context "with an array of columns and an array of values" do
166
+ it "should import ids when specified" do
167
+ Topic.import [:id, :author_name, :title], [[99, "Bob Jones", "Topic 99"]]
168
+ assert_equal 99, Topic.last.id
169
+ end
170
+ end
171
+
138
172
  context "ActiveRecord timestamps" do
139
173
  context "when the timestamps columns are present" do
140
174
  setup do
@@ -210,5 +244,4 @@ describe "#import" do
210
244
  assert_equal "05/14/2010".to_date, Topic.last.last_read.to_date
211
245
  end
212
246
  end
213
-
214
247
  end
@@ -142,4 +142,30 @@ def should_support_mysql_import_functionality
142
142
  end
143
143
 
144
144
  end
145
+
146
+ describe "#import with :synchronization option" do
147
+ let(:topics){ Array.new }
148
+ let(:values){ [ [topics.first.id, "Jerry Carter"], [topics.last.id, "Chad Fowler"] ]}
149
+ let(:columns){ %W(id author_name) }
150
+
151
+ setup do
152
+ topics << Topic.create!(:title=>"LDAP", :author_name=>"Big Bird")
153
+ topics << Topic.create!(:title=>"Rails Recipes", :author_name=>"Elmo")
154
+ end
155
+
156
+ it "synchronizes passed in ActiveRecord model instances with the data just imported" do
157
+ columns2update = [ 'author_name' ]
158
+
159
+ expected_count = Topic.count
160
+ Topic.import( columns, values,
161
+ :validate=>false,
162
+ :on_duplicate_key_update=>columns2update,
163
+ :synchronize=>topics )
164
+
165
+ assert_equal expected_count, Topic.count, "no new records should have been created!"
166
+ assert_equal "Jerry Carter", topics.first.author_name, "wrong author!"
167
+ assert_equal "Chad Fowler", topics.last.author_name, "wrong author!"
168
+ end
169
+ end
170
+
145
171
  end
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe ".synchronize" do
4
+ let(:topics){ Generate(3, :topics) }
5
+ let(:titles){ %w(one two three) }
6
+
7
+ setup do
8
+ # update records outside of ActiveRecord knowing about it
9
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[0]}_haha' WHERE id=#{topics[0].id}", "Updating record 1 without ActiveRecord" )
10
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[1]}_haha' WHERE id=#{topics[1].id}", "Updating record 2 without ActiveRecord" )
11
+ Topic.connection.execute( "UPDATE #{Topic.table_name} SET title='#{titles[2]}_haha' WHERE id=#{topics[2].id}", "Updating record 3 without ActiveRecord" )
12
+ end
13
+
14
+ it "reloads data for the specified records" do
15
+ Book.synchronize topics
16
+
17
+ actual_titles = topics.map(&:title)
18
+ assert_equal "#{titles[0]}_haha", actual_titles[0], "the first record was not correctly updated"
19
+ assert_equal "#{titles[1]}_haha", actual_titles[1], "the second record was not correctly updated"
20
+ assert_equal "#{titles[2]}_haha", actual_titles[2], "the third record was not correctly updated"
21
+ end
22
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-import
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 5
10
- version: 0.2.5
9
+ - 6
10
+ version: 0.2.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Zach Dennis
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-11 00:00:00 -05:00
18
+ date: 2011-04-06 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -42,10 +42,8 @@ extensions: []
42
42
 
43
43
  extra_rdoc_files:
44
44
  - README.markdown
45
- - README.textile
46
45
  files:
47
46
  - README.markdown
48
- - README.textile
49
47
  - Rakefile
50
48
  - VERSION
51
49
  - lib/activerecord-import.rb
@@ -64,6 +62,7 @@ files:
64
62
  - lib/activerecord-import/mysql2.rb
65
63
  - lib/activerecord-import/postgresql.rb
66
64
  - lib/activerecord-import/sqlite3.rb
65
+ - lib/activerecord-import/synchronize.rb
67
66
  - test/active_record/connection_adapter_test.rb
68
67
  - test/adapters/mysql.rb
69
68
  - test/adapters/mysql2.rb
@@ -84,6 +83,7 @@ files:
84
83
  - test/support/generate.rb
85
84
  - test/support/mysql/assertions.rb
86
85
  - test/support/mysql/import_examples.rb
86
+ - test/synchronize_test.rb
87
87
  - test/test_helper.rb
88
88
  has_rdoc: true
89
89
  homepage: http://github.com/zdennis/activerecord-import
@@ -140,4 +140,5 @@ test_files:
140
140
  - test/support/generate.rb
141
141
  - test/support/mysql/assertions.rb
142
142
  - test/support/mysql/import_examples.rb
143
+ - test/synchronize_test.rb
143
144
  - test/test_helper.rb
data/README.textile DELETED
@@ -1,62 +0,0 @@
1
- h1. activerecord-import
2
-
3
- activerecord-import is a library for bulk inserting data using ActiveRecord.
4
-
5
- h2. Why activerecord-import?
6
-
7
- Because plain-vanilla, out-of-the-box ActiveRecord doesn't provide support for inserting large amounts of data efficiently. With vanilla ActiveRecord you would have to perform individual save operations on each model:
8
-
9
- <pre>
10
- 10.times do |i|
11
- Book.create! :name => "book #{i}"
12
- end
13
- </pre>
14
-
15
- This may work fine if all you have is 10 records, but if you have hundreds, thousands, or millions of records it can turn into a nightmare. This is where activerecord-import comes into play.
16
-
17
- h2. An Introductory Example
18
-
19
- Here's an example with equivalent behaviour using the @#import@ method:
20
-
21
- <pre>
22
- books = []
23
- 10.times do |i|
24
- books << Book.new(:name => "book #{i}")
25
- end
26
- Book.import books
27
- </pre>
28
-
29
- This call to import does whatever is most efficient for the underlying database adapter. Pretty slick, eh?
30
-
31
- h2. Features
32
-
33
- Here's a list of some of the high-level features that activerecord-import provides:
34
-
35
- * activerecord-import can work with raw columns and arrays of values (fastest)
36
- * activerecord-import works with model objects (faster)
37
- * activerecord-import can perform validations (fast)
38
- * activerecord-import can perform on duplicate key updates (requires mysql)
39
-
40
-
41
- h1. Upgrading from ar-extensions
42
-
43
- activerecord-import replaces the ar-extensions library and is compatible with Rails 3. It provides the exact same API for importing data, but it does not include any additional ar-extensions functionality.
44
-
45
- h1. License
46
-
47
- This is licensed under the ruby license.
48
-
49
- h1. Author
50
-
51
- Zach Dennis (zach.dennis@gmail.com)
52
-
53
- h1. Contributors
54
-
55
- * Blythe Dunham
56
- * Gabe da Silveira
57
- * Henry Work
58
- * James Herdman
59
- * Marcus Crafter
60
- * Thibaud Guillaume-Gentil
61
- * Mark Van Holstyn
62
- * Victor Costan