model_iterator 1.0.1 → 1.0.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/README.md +11 -7
- data/lib/model_iterator.rb +12 -5
- data/model_iterator.gemspec +4 -2
- data/script/bootstrap +6 -0
- data/script/test +5 -0
- data/test/init_test.rb +75 -0
- data/test/iterate_test.rb +24 -0
- metadata +5 -3
data/README.md
CHANGED
|
@@ -4,17 +4,21 @@ Basic library for iterating through large ActiveRecord datasets. For instance,
|
|
|
4
4
|
let's say you add a new feature, and you need to backfill data for existing
|
|
5
5
|
records:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
```ruby
|
|
8
|
+
iter = ModelIterator.new(User, :redis => $redis)
|
|
9
|
+
iter.each do |user|
|
|
10
|
+
backfill(user)
|
|
11
|
+
end
|
|
12
|
+
```
|
|
11
13
|
|
|
12
14
|
ModelIterator selects the records in batches (100 by default), and loops
|
|
13
15
|
through the table filtering based on the ID.
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
```sql
|
|
18
|
+
SELECT * FROM users WHERE id > 0 LIMIT 100
|
|
19
|
+
SELECT * FROM users WHERE id > 100 LIMIT 100
|
|
20
|
+
SELECT * FROM users WHERE id > 200 LIMIT 100
|
|
21
|
+
```
|
|
18
22
|
|
|
19
23
|
Each record's ID is tracked in Redis immediately after being processed. If
|
|
20
24
|
jobs crash, you can fix code, and re-run from where you left off.
|
data/lib/model_iterator.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Iterates over large models, storing state in Redis.
|
|
2
2
|
class ModelIterator
|
|
3
|
-
VERSION = "1.0.
|
|
3
|
+
VERSION = "1.0.2"
|
|
4
4
|
|
|
5
5
|
class MaxIterations < StandardError
|
|
6
6
|
attr_reader :iterator
|
|
@@ -83,6 +83,8 @@ class ModelIterator
|
|
|
83
83
|
# at a time.
|
|
84
84
|
# :limit - Fixnum limit of objects to fetch from the db.
|
|
85
85
|
# Default: 100
|
|
86
|
+
# :conditions - Array of String SQL WHERE clause and optional values
|
|
87
|
+
# (Will override clause/values given in arguments.)
|
|
86
88
|
#
|
|
87
89
|
# ModelIterator.new(Repository, :start_id => 5000)
|
|
88
90
|
# ModelIterator.new(Repository, 'public=?', true, :start_id => 1000)
|
|
@@ -101,10 +103,15 @@ class ModelIterator
|
|
|
101
103
|
op = @order == :asc ? '>' : '<'
|
|
102
104
|
@max = @options[:max].to_i
|
|
103
105
|
@joins = @options[:joins]
|
|
104
|
-
@clause =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
@clause = "#{@id_clause} #{op} ?"
|
|
107
|
+
if @options[:conditions]
|
|
108
|
+
conditions = Array(@options[:conditions])
|
|
109
|
+
@clause += " AND (#{conditions.first})"
|
|
110
|
+
@clause_args = conditions[1..-1]
|
|
111
|
+
elsif !args.empty?
|
|
112
|
+
@clause += " AND (#{args.shift})"
|
|
113
|
+
@clause_args = args
|
|
114
|
+
end
|
|
108
115
|
@current_id = @options[:start_id]
|
|
109
116
|
@limit = @options[:limit] || 100
|
|
110
117
|
@job = @prefix = @key = nil
|
data/model_iterator.gemspec
CHANGED
|
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
## If your rubyforge_project name is different, then edit it and comment out
|
|
13
13
|
## the sub! line in the Rakefile
|
|
14
14
|
s.name = 'model_iterator'
|
|
15
|
-
s.version = '1.0.
|
|
16
|
-
s.date = '
|
|
15
|
+
s.version = '1.0.2'
|
|
16
|
+
s.date = '2013-03-19'
|
|
17
17
|
s.rubyforge_project = 'model_iterator'
|
|
18
18
|
|
|
19
19
|
## Make sure your summary is short. The description may be as long
|
|
@@ -46,6 +46,8 @@ Gem::Specification.new do |s|
|
|
|
46
46
|
Rakefile
|
|
47
47
|
lib/model_iterator.rb
|
|
48
48
|
model_iterator.gemspec
|
|
49
|
+
script/bootstrap
|
|
50
|
+
script/test
|
|
49
51
|
test/helper.rb
|
|
50
52
|
test/init_test.rb
|
|
51
53
|
test/iterate_test.rb
|
data/script/bootstrap
ADDED
data/script/test
ADDED
data/test/init_test.rb
CHANGED
|
@@ -87,3 +87,78 @@ class InitializationTestWithCustomWhereClause < ModelIterator::TestCase
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
class InitializationTestWithCustomWhereClauseInOptions < ModelIterator::TestCase
|
|
91
|
+
def setup
|
|
92
|
+
@iter = ModelIterator.new Model,
|
|
93
|
+
:redis => RedisClient.new, :start_id => 5, :limit => 10,
|
|
94
|
+
:conditions => ['public = ?', true]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_sets_klass
|
|
98
|
+
assert_equal Model, @iter.klass
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_sets_current_id
|
|
102
|
+
assert_equal 5, @iter.current_id
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_sets_conditions
|
|
106
|
+
assert_equal ['models.id > ? AND (public = ?)', @iter.current_id, true], @iter.conditions
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_sets_limit
|
|
110
|
+
assert_equal 10, @iter.limit
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class InitializationTestWithCustomWhereClauseInOptionsOverridingArguments < ModelIterator::TestCase
|
|
115
|
+
def setup
|
|
116
|
+
@iter = ModelIterator.new Model,
|
|
117
|
+
'unknown = ?',
|
|
118
|
+
false,
|
|
119
|
+
:redis => RedisClient.new, :start_id => 5, :limit => 10,
|
|
120
|
+
:conditions => ['public = ?', true]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_sets_klass
|
|
124
|
+
assert_equal Model, @iter.klass
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_sets_current_id
|
|
128
|
+
assert_equal 5, @iter.current_id
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_sets_conditions
|
|
132
|
+
assert_equal ['models.id > ? AND (public = ?)', @iter.current_id, true], @iter.conditions
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_sets_limit
|
|
136
|
+
assert_equal 10, @iter.limit
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class InitializationTestWithCustomWhereClauseInOptionsAsSql < ModelIterator::TestCase
|
|
141
|
+
def setup
|
|
142
|
+
@iter = ModelIterator.new Model,
|
|
143
|
+
:redis => RedisClient.new, :start_id => 5, :limit => 10,
|
|
144
|
+
:conditions => 'public = true'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_sets_klass
|
|
148
|
+
assert_equal Model, @iter.klass
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_sets_current_id
|
|
152
|
+
assert_equal 5, @iter.current_id
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_sets_conditions
|
|
156
|
+
assert_equal ['models.id > ? AND (public = true)', @iter.current_id], @iter.conditions
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_sets_limit
|
|
160
|
+
assert_equal 10, @iter.limit
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
|
data/test/iterate_test.rb
CHANGED
|
@@ -27,6 +27,30 @@ class IterateTest < ModelIterator::TestCase
|
|
|
27
27
|
assert_equal %w(b c), names
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def test_loops_through_filtered_records_from_options
|
|
31
|
+
names = []
|
|
32
|
+
iter = ModelIterator.new Model,
|
|
33
|
+
:redis => RedisClient.new, :limit => 1,
|
|
34
|
+
:conditions => ['name != ?', 'a']
|
|
35
|
+
iter.each do |m|
|
|
36
|
+
names << m.name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
assert_equal %w(b c), names
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_loops_through_filtered_records_from_options_as_plain_sql
|
|
43
|
+
names = []
|
|
44
|
+
iter = ModelIterator.new Model,
|
|
45
|
+
:redis => RedisClient.new, :limit => 1,
|
|
46
|
+
:conditions => "name != 'a'"
|
|
47
|
+
iter.each do |m|
|
|
48
|
+
names << m.name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert_equal %w(b c), names
|
|
52
|
+
end
|
|
53
|
+
|
|
30
54
|
def test_loops_through_records_in_reverse
|
|
31
55
|
names = []
|
|
32
56
|
iter = ModelIterator.new Model, :redis => RedisClient.new, :limit => 1,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: model_iterator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2013-03-19 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rake
|
|
@@ -55,6 +55,8 @@ files:
|
|
|
55
55
|
- Rakefile
|
|
56
56
|
- lib/model_iterator.rb
|
|
57
57
|
- model_iterator.gemspec
|
|
58
|
+
- script/bootstrap
|
|
59
|
+
- script/test
|
|
58
60
|
- test/helper.rb
|
|
59
61
|
- test/init_test.rb
|
|
60
62
|
- test/iterate_test.rb
|
|
@@ -72,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
72
74
|
version: '0'
|
|
73
75
|
segments:
|
|
74
76
|
- 0
|
|
75
|
-
hash:
|
|
77
|
+
hash: 1002102300965983738
|
|
76
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
79
|
none: false
|
|
78
80
|
requirements:
|