bulk_update 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +72 -0
- data/Rakefile +4 -0
- data/bulk_update.gemspec +22 -0
- data/lib/bulk_update.rb +5 -0
- data/lib/bulk_update/active_record_inflections.rb +261 -0
- data/lib/bulk_update/version.rb +3 -0
- data/spec/bulk_update_spec.rb +29 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/active_record.rb +39 -0
- data/spec/support/my_hash.rb +4 -0
- data/spec/support/schema.rb +5 -0
- metadata +129 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Philip Kurmann
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# BulkUpdate
|
2
|
+
|
3
|
+
Updates a large amount of Records in a highliy efficient way.
|
4
|
+
Enhances Active Record with a method for bulk inserts and a method for bulk updates. Both merthods are used for inserting or updating large amount of Records.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'bulk_update'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install bulk_update
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Bulk insert
|
23
|
+
|
24
|
+
Bulk insert inserts a large amount of data into as SQL-Bulk-Inserts. Example:
|
25
|
+
columns = [:name, :value]
|
26
|
+
values = [['name1', 'value1'], ['name2', 'value2'], ['name3', 'value3']]
|
27
|
+
Model.bulk_insert columns, values
|
28
|
+
|
29
|
+
### Bulk update
|
30
|
+
|
31
|
+
Bulk update updates a large amount of data. Update means, it creates new records, updates existing records which have changed and deletes old records. This is all done with ActiveRecord which means, all callbacks are executed.
|
32
|
+
You have to provide a columns as a key, which is used determine which records are new, have changed or has to be deleted. Only the values provided in the array 'values' are compared and will be updated. Example:
|
33
|
+
|
34
|
+
columns = [:name, :value]
|
35
|
+
values = [['name1', 'value1'], ['name2', 'value2'], ['name3', 'value3']]
|
36
|
+
Model.bulk_insert columns, values
|
37
|
+
|
38
|
+
You have now the following entries in your database:
|
39
|
+
<pre>
|
40
|
+
+----+----------------+
|
41
|
+
| id | name | value |
|
42
|
+
+----+----------------+
|
43
|
+
| 0 | name1 | value1 |
|
44
|
+
| 1 | name2 | value2 |
|
45
|
+
| 2 | name3 | value3 |
|
46
|
+
+----+----------------+
|
47
|
+
</pre>
|
48
|
+
|
49
|
+
If you now do a bulk update:
|
50
|
+
|
51
|
+
values = [['name1', 'value1.1'], ['name2', 'value2'], ['name4', 'value4.1']]
|
52
|
+
Model.bulk_update columns, values, key: 'name'
|
53
|
+
|
54
|
+
You have now the following entries in your database:
|
55
|
+
<pre>
|
56
|
+
+----+------------------+
|
57
|
+
| id | name | value |
|
58
|
+
+----+------------------+
|
59
|
+
| 0 | name1 | value1.1 |
|
60
|
+
| 1 | name2 | value2 |
|
61
|
+
| 3 | name4 | value4.1 |
|
62
|
+
+----+------------------+
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
1. Fork it
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
72
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bulk_update.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/bulk_update/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Philip Kurmann"]
|
6
|
+
gem.email = ["philip@kman.ch"]
|
7
|
+
gem.description = %q{Updates a large amount of Records in a highly efficient way}
|
8
|
+
gem.summary = %q{Enhances Active Record with a method for bulk inserts and a method for bulk updates. Both merthods are used for inserting or updating large amount of Records.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.add_dependency "activerecord"
|
12
|
+
gem.add_development_dependency "rspec"
|
13
|
+
gem.add_development_dependency "sqlite3"
|
14
|
+
gem.add_development_dependency "pry"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($\)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.name = "bulk_update"
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
gem.version = BulkUpdate::VERSION
|
22
|
+
end
|
data/lib/bulk_update.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
module BulkUpdate
|
2
|
+
module ActiveRecordInflections
|
3
|
+
#
|
4
|
+
# Clone the database structure of a table
|
5
|
+
def clone_table args = {}
|
6
|
+
if args[:to]
|
7
|
+
case ActiveRecord::Base.connection_config[:adapter]
|
8
|
+
when 'sqlite3'
|
9
|
+
ActiveRecord::Base.connection.execute "CREATE TABLE `#{args[:to]}` AS SELECT * FROM `#{table_name}` LIMIT 1"
|
10
|
+
ActiveRecord::Base.connection.execute "DELETE FROM `#{args[:to]}`"
|
11
|
+
else
|
12
|
+
ActiveRecord::Base.connection.execute "CREATE TABLE `#{args[:to]}` LIKE `#{table_name}`"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def insert_str element
|
19
|
+
if element.class == Fixnum || element.class == Float
|
20
|
+
element
|
21
|
+
elsif element.class == NilClass
|
22
|
+
'NULL'
|
23
|
+
else
|
24
|
+
if element.to_s[0] == '(' || element.to_s.downcase == 'true' || element.to_s.downcase == 'false'
|
25
|
+
element.to_s
|
26
|
+
else
|
27
|
+
"'#{element}'"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
#
|
34
|
+
# Bulk insert records
|
35
|
+
def bulk_insert columns, values, args = {}
|
36
|
+
# Limit inserts
|
37
|
+
max_records_per_insert = args[:max_records_per_insert] || 100
|
38
|
+
table = args[:into] || table_name
|
39
|
+
columns = columns.clone
|
40
|
+
|
41
|
+
# Add timestamp
|
42
|
+
timestamp = Time.now
|
43
|
+
add_timestamp = false
|
44
|
+
add_updated_at = false
|
45
|
+
unless columns.map(&:to_sym).include?(:created_at)
|
46
|
+
columns << :created_at
|
47
|
+
add_created_at = true
|
48
|
+
end
|
49
|
+
unless columns.map(&:to_sym).include?(:updated_at)
|
50
|
+
columns << :updated_at
|
51
|
+
add_updated_at = true
|
52
|
+
end
|
53
|
+
|
54
|
+
if table_name
|
55
|
+
# Create header for insert with all column names
|
56
|
+
columns = columns.clone.map!{ |c| "`#{c}`" }
|
57
|
+
insert_head = "INSERT INTO `#{table}` (#{columns.join(', ')})"
|
58
|
+
|
59
|
+
# Create inserts
|
60
|
+
inserts = []
|
61
|
+
values.each do |values_per_record|
|
62
|
+
values_per_record = values_per_record.clone
|
63
|
+
values_per_record << timestamp if add_created_at
|
64
|
+
values_per_record << timestamp if add_updated_at
|
65
|
+
inserts << "(#{values_per_record.map{ |e| insert_str(e) }.join(', ')})"
|
66
|
+
if inserts.count > max_records_per_insert
|
67
|
+
ActiveRecord::Base.connection.execute "#{insert_head} VALUES #{inserts.join(', ')}"
|
68
|
+
inserts.clear
|
69
|
+
end
|
70
|
+
end
|
71
|
+
ActiveRecord::Base.connection.execute "#{insert_head} VALUES #{inserts.join(', ')}" unless inserts.empty?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
#
|
77
|
+
# Create, update and delete Records according to a set of new values through ActiveRecord but optimized for performance by
|
78
|
+
# finding all diferences by SQL.
|
79
|
+
def bulk_update columns, values, args = {}
|
80
|
+
temp_table = "#{table_name}_temp_table_#{$$}"
|
81
|
+
key = args[:key] || args[:keys] || 'id'
|
82
|
+
condition = args[:condition]
|
83
|
+
exclude_fields = args[:exclude_fields]
|
84
|
+
insert = args[:insert].nil? ? true : args[:insert]
|
85
|
+
update = args[:update].nil? ? true : args[:update]
|
86
|
+
remove = args[:remove].nil? ? true : args[:remove]
|
87
|
+
|
88
|
+
# Clone temp-table and load it
|
89
|
+
clone_table to: temp_table
|
90
|
+
bulk_insert columns, values, into: temp_table
|
91
|
+
|
92
|
+
# Find differences and create, update and delete these through ActiveRecord to handle Callbacks, etc.
|
93
|
+
create(get_new_records for: self, compare_with: temp_table, on: key, condition: condition, exclude_fields: exclude_fields) if insert
|
94
|
+
if update
|
95
|
+
get_updated_records(for: self, compare_with: temp_table, on: key, condition: condition, exclude_virtual: args[:exclude_virtual], exclude_fields: exclude_fields).each do |id, new_attributes|
|
96
|
+
find(id).update_attributes new_attributes
|
97
|
+
end
|
98
|
+
end
|
99
|
+
destroy(get_deleted_records for: self, compare_with: temp_table, on: key, condition: condition, exclude_virtual: args[:exclude_virtual]) if remove
|
100
|
+
|
101
|
+
ensure
|
102
|
+
# Drop temp table
|
103
|
+
ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS #{temp_table}"
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
#
|
108
|
+
# Exclude List
|
109
|
+
def default_exclude
|
110
|
+
['id', 'version', 'created_at', 'updated_at']
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
#
|
115
|
+
# Compare Table of args[:model] with its temporary table args[:compare_with] and return all new records as a Array of Hashes
|
116
|
+
def get_new_records args = {}
|
117
|
+
model = args[:for] || self
|
118
|
+
compare_table = args[:compare_with]
|
119
|
+
keys = args[:on] || 'id'
|
120
|
+
exclude = args[:exclude_fields] || []
|
121
|
+
exclude |= default_exclude
|
122
|
+
|
123
|
+
# Generate conditions for query and sub-query
|
124
|
+
conditions = []
|
125
|
+
conditions2 = []
|
126
|
+
conditions << "#{args[:condition].gsub('--tt--', compare_table)}" if args[:condition]
|
127
|
+
conditions2 << "#{args[:condition].gsub('--tt--', model.table_name)}" if args[:condition]
|
128
|
+
if keys.class == String || keys.class == Symbol
|
129
|
+
key = keys.to_s
|
130
|
+
else
|
131
|
+
key = keys[0].to_s
|
132
|
+
conditions2 << keys[1..-1].map{|k| "#{model.table_name}.#{k.to_s} = #{compare_table}.#{k.to_s}" }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Generate and execute SQL-Statement
|
136
|
+
condition = conditions.join(' AND ')
|
137
|
+
condition2 = conditions2.join(' AND ')
|
138
|
+
sql = "SELECT * FROM #{compare_table} WHERE #{condition} #{'AND' unless conditions.blank?} #{compare_table}.#{key} NOT IN " +
|
139
|
+
"(SELECT #{key} FROM #{model.table_name} #{'WHERE' unless conditions2.blank?} #{condition2})"
|
140
|
+
results = ActiveRecord::Base.connection.execute sql
|
141
|
+
|
142
|
+
# Generate Array with Hashes of all new records
|
143
|
+
new_records = []
|
144
|
+
keys_to_log = []
|
145
|
+
results.each do |attributes|
|
146
|
+
new_records << result2hash(attributes, exclude)
|
147
|
+
keys_to_log << new_records.last[key.to_sym]
|
148
|
+
end
|
149
|
+
args[:logger].info "New Records for Model #{model.to_s}: #{keys_to_log.join(', ')}" unless keys_to_log.blank? || args[:logger].blank?
|
150
|
+
new_records
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
#
|
155
|
+
# Compare Table of args[:model] with its temporary table args[:compare_with] and return all updated records as a Hash of Hashes whose
|
156
|
+
# key is the ID of the changed record
|
157
|
+
def get_updated_records args = {}
|
158
|
+
model = args[:for] || self
|
159
|
+
compare_table = args[:compare_with]
|
160
|
+
keys = args[:on] || 'id'
|
161
|
+
exclude = args[:exclude_fields] || []
|
162
|
+
exclude |= default_exclude
|
163
|
+
exclude_virtual = args[:exclude_virtual].nil? ? false : args[:exclude_virtual]
|
164
|
+
|
165
|
+
# Generate conditions for query and sub-query
|
166
|
+
conditions = []
|
167
|
+
conditions2 = []
|
168
|
+
conditions << "NOT #{model.table_name}.virtual" if exclude_virtual
|
169
|
+
if keys.class == String || keys.class == Symbol
|
170
|
+
key = keys.to_s
|
171
|
+
conditions << "#{model.table_name}.#{key} = #{compare_table}.#{key}"
|
172
|
+
exclude |= [keys.to_s]
|
173
|
+
else
|
174
|
+
key = keys[0].to_s
|
175
|
+
conditions |= keys.map{|k| "#{model.table_name}.#{k.to_s} = #{compare_table}.#{k.to_s}" }
|
176
|
+
exclude |= keys.map(&:to_s)
|
177
|
+
end
|
178
|
+
conditions << "#{args[:condition].gsub('--tt--', model.table_name)} AND #{args[:condition].gsub('--tt--', compare_table)}" if args[:condition]
|
179
|
+
model.attribute_names.each do |an|
|
180
|
+
unless exclude.include?(an)
|
181
|
+
if ActiveRecord::Base.connection_config[:adapter] =~ /mysql?/
|
182
|
+
conditions2 << "NOT #{model.table_name}.#{an} <=> #{compare_table}.#{an}" unless exclude.include?(an)
|
183
|
+
else
|
184
|
+
conditions2 << "NOT (#{model.table_name}.#{an} = #{compare_table}.#{an} OR (#{model.table_name}.#{an} IS NULL AND #{compare_table}.#{an} IS NULL))"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Generate and execute SQL-Statement
|
190
|
+
condition = "#{conditions.join(' AND ')} AND (#{conditions2.join(' OR ')})"
|
191
|
+
sql = "SELECT #{model.table_name}.id, #{compare_table}.* FROM #{model.table_name}, #{compare_table} WHERE #{condition}"
|
192
|
+
results = ActiveRecord::Base.connection.execute sql
|
193
|
+
|
194
|
+
# Generate Hash with id as the key and values as a Hashes of all changed records
|
195
|
+
results_hash = {}
|
196
|
+
keys_to_log = []
|
197
|
+
results.each do |attributes|
|
198
|
+
id = attributes[0]
|
199
|
+
results_hash[id] = result2hash attributes, exclude, 1
|
200
|
+
keys_to_log << (args[:debug] ? model.find(id).send(key) : id)
|
201
|
+
end
|
202
|
+
args[:logger].info "Change Records for Model #{model.to_s}: #{keys_to_log.join(', ')}" unless keys_to_log.blank? || args[:logger].blank?
|
203
|
+
|
204
|
+
results_hash
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
#
|
209
|
+
# Compare Table of args[:model] with its temporary table args[:compare_with] and return all deleted records as a Array of IDs
|
210
|
+
def get_deleted_records args = {}
|
211
|
+
model = args[:for] || self
|
212
|
+
compare_table = args[:compare_with]
|
213
|
+
keys = args[:on] || 'id'
|
214
|
+
exclude_virtual = args[:exclude_virtual].nil? ? false : args[:exclude_virtual]
|
215
|
+
|
216
|
+
# Generate conditions for query and sub-query
|
217
|
+
conditions = []
|
218
|
+
conditions2 = []
|
219
|
+
conditions << "NOT #{model.table_name}.virtual" if exclude_virtual
|
220
|
+
conditions << "#{args[:condition].gsub('--tt--', model.table_name)}" if args[:condition]
|
221
|
+
conditions2 << "#{args[:condition].gsub('--tt--', compare_table)}" if args[:condition]
|
222
|
+
if keys.class == String || keys.class == Symbol
|
223
|
+
key = keys.to_s
|
224
|
+
else
|
225
|
+
key = keys[0].to_s
|
226
|
+
conditions2 |= keys[1..-1].map{|k| "#{model.table_name}.#{k.to_s} = #{compare_table}.#{k.to_s}" }
|
227
|
+
end
|
228
|
+
|
229
|
+
# Generate and execute SQL-Statement
|
230
|
+
condition = conditions.join(' AND ') unless conditions.blank?
|
231
|
+
condition2 = conditions2.join(' AND ') unless conditions2.blank?
|
232
|
+
sql = "SELECT id, #{key} FROM #{model.table_name} WHERE #{condition} #{'AND' unless conditions.blank?} #{model.table_name}.#{key} NOT IN " +
|
233
|
+
"(SELECT #{key} FROM #{compare_table} #{'WHERE' unless conditions2.blank?} #{condition2})"
|
234
|
+
results = ActiveRecord::Base.connection.execute sql
|
235
|
+
|
236
|
+
# Generate Array with ids of all deleted records
|
237
|
+
deleted_records = []
|
238
|
+
keys_to_log = []
|
239
|
+
results.each do |attributes|
|
240
|
+
deleted_records << attributes[0]
|
241
|
+
keys_to_log << attributes[1]
|
242
|
+
end
|
243
|
+
args[:logger].info "Deleting Records from Model #{model.to_s}: #{keys_to_log.join(', ')}" unless keys_to_log.blank? || args[:logger].blank?
|
244
|
+
deleted_records
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
|
251
|
+
def result2hash attributes, exclude, attribute_nr = 0
|
252
|
+
hash = {}
|
253
|
+
attribute_names.each do |an|
|
254
|
+
hash[an.to_sym] = attributes[attribute_nr] unless exclude.include?(an)
|
255
|
+
attribute_nr += 1
|
256
|
+
end
|
257
|
+
hash
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
describe BulkUpdate do
|
6
|
+
before :each do
|
7
|
+
@columns = [:name, :value]
|
8
|
+
@values = [['test1', 'value1'], ['test2', 'value2'], ['test3', 'value3'], ['test4', 'value4']]
|
9
|
+
MyHash.bulk_insert @columns, @values
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
it 'inserts multiple records in one SQL' do
|
14
|
+
MyHash.count.should be 4
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
it 'updates and deletes records' do
|
19
|
+
@values = [['test1', 'value1.1'], ['test2', 'value2'], ['test4', 'value4.4'], ['test5', 'value5.5']]
|
20
|
+
MyHash.bulk_update @columns, @values, key: 'name'
|
21
|
+
MyHash.count.should be 4
|
22
|
+
MyHash.where(name: 'test1').first.value.should eq 'value1.1'
|
23
|
+
MyHash.where(name: 'test2').first.value.should eq 'value2'
|
24
|
+
MyHash.where(name: 'test3').first.should be nil
|
25
|
+
MyHash.where(name: 'test4').first.value.should eq 'value4.4'
|
26
|
+
MyHash.where(name: 'test5').first.value.should eq 'value5.5'
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/version'
|
3
|
+
puts "Testing ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
6
|
+
ActiveRecord::Migrator.up "db/migrate"
|
7
|
+
|
8
|
+
load 'spec/support/schema.rb'
|
9
|
+
|
10
|
+
|
11
|
+
module ActiveModel::Validations
|
12
|
+
# Extension to enhance `should have` on AR Model instances. Calls
|
13
|
+
# model.valid? in order to prepare the object's errors object.
|
14
|
+
#
|
15
|
+
# You can also use this to specify the content of the error messages.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# model.should have(:no).errors_on(:attribute)
|
20
|
+
# model.should have(1).error_on(:attribute)
|
21
|
+
# model.should have(n).errors_on(:attribute)
|
22
|
+
#
|
23
|
+
# model.errors_on(:attribute).should include("can't be blank")
|
24
|
+
def errors_on(attribute)
|
25
|
+
self.valid?
|
26
|
+
[self.errors[attribute]].flatten.compact
|
27
|
+
end
|
28
|
+
alias :error_on :errors_on
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.around do |example|
|
34
|
+
ActiveRecord::Base.transaction do
|
35
|
+
example.run
|
36
|
+
raise ActiveRecord::Rollback
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bulk_update
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Philip Kurmann
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Updates a large amount of Records in a highly efficient way
|
79
|
+
email:
|
80
|
+
- philip@kman.ch
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- Gemfile
|
87
|
+
- LICENSE
|
88
|
+
- README.md
|
89
|
+
- Rakefile
|
90
|
+
- bulk_update.gemspec
|
91
|
+
- lib/bulk_update.rb
|
92
|
+
- lib/bulk_update/active_record_inflections.rb
|
93
|
+
- lib/bulk_update/version.rb
|
94
|
+
- spec/bulk_update_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/support/active_record.rb
|
97
|
+
- spec/support/my_hash.rb
|
98
|
+
- spec/support/schema.rb
|
99
|
+
homepage: ''
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.8.24
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: Enhances Active Record with a method for bulk inserts and a method for bulk
|
123
|
+
updates. Both merthods are used for inserting or updating large amount of Records.
|
124
|
+
test_files:
|
125
|
+
- spec/bulk_update_spec.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
- spec/support/active_record.rb
|
128
|
+
- spec/support/my_hash.rb
|
129
|
+
- spec/support/schema.rb
|