scripterator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2ViZDdmMmFjMWUxYzIyMTlmMDg4OTYzZmFlZWNkZDkyNWExMzEyYQ==
5
+ data.tar.gz: !binary |-
6
+ MDEzMzJkNjdkNjRhZWVhODE2YTIwMjY2NWY2NDdiNGRhM2VmYmM0ZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MGQxNmIzOTBlMjg5MjE5ZTJlZGVkNjM3ZTNjNzg0MGI1NjFmODZhMDBjNzgy
10
+ NjE3OGNlNmY3MGRjNDg5MjA1ODc2ODgwOTNmMzY0MmZlMWMzN2MzZjk5M2M5
11
+ ZWYzZjVhYWFjZTU2MDQ1YjFiMzg5NWZhMGJhYzkyOWMxNThmZmI=
12
+ data.tar.gz: !binary |-
13
+ ZGYxYzQyZWUwMGFjZTJmZWE5MmMyMjRhMDZiMmRjMWU0NTgyYjZkNTU2Y2Qx
14
+ YmM3YTE0OWZmNDJhYmMyOWE2ZTNmZTkwMDA3YTg4NDU4MTA1MjI0M2YwMzYx
15
+ ZDk4ZTViYzc2NThmNWYwMjZlYWU3ZTYzZDI0MTQwZjA3ZWRhNWY=
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scripterator.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 t ddddddd
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.
@@ -0,0 +1,123 @@
1
+ # Scripterator
2
+ "Helping you scripterate over all the things"
3
+
4
+ A lightweight script harness and DSL for iterating over and running operations on ActiveRecord model records, with Redis hooks for managing subsets, failures, and retries.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'scripterator'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install scripterator
19
+
20
+ Works with ActiveRecord 3.* (Rails 4 support coming).
21
+
22
+ ## Usage
23
+
24
+ Create a .rb file with your script code:
25
+ ```ruby
26
+ Scripterator.run "Convert users from legacy auth data" do
27
+
28
+ before do
29
+ User.skip_some_callbacks_we_want_to_avoid_during_script_running
30
+ end
31
+
32
+ for_each_user do |user|
33
+ user.do_legacy_conversion
34
+ end
35
+
36
+ after do
37
+ # some code to run after everything's finished
38
+ end
39
+
40
+ end
41
+ ```
42
+
43
+ Run your script for a given set of IDs:
44
+
45
+ $ START=10000 END=19999 bundle exec rails runner my_script.rb >out.txt
46
+
47
+ This will produce output of the form:
48
+
49
+ ```
50
+ Starting at 2013-09-24 14:53:39 -0700...
51
+ 2013-09-24 14:53:40 -0700: Checked 0 rows, 0 migrated.
52
+ 2013-09-24 14:53:41 -0700: Checked 10000 rows, 10000 migrated.
53
+ 2013-09-24 14:53:41 -0700: Checked 20000 rows, 20000 migrated.
54
+ 2013-09-24 14:53:42 -0700: Checked 30000 rows, 30000 migrated.
55
+ done
56
+ Finished at 2013-09-24 14:53:43 -0700...
57
+
58
+ Total rows migrated: 34903 / 34903
59
+ 0 rows previously migrated and skipped
60
+ 0 errors
61
+ ```
62
+
63
+ Retrieve set information about checked and failed records:
64
+
65
+ ```
66
+ > Scripterator.failed_ids_for "Convert users from legacy auth data"
67
+ => [14011, 15634, 17301, 17302]
68
+
69
+ > Scripterator.already_run_for?("Convert users from legacy auth data", 15000)
70
+ => true
71
+ ```
72
+
73
+ User-definable blocks:
74
+
75
+ Required:
76
+
77
+ - `for_each_(.+)`: code to run for every record. This block should return `true` (or a truthy value) if the operation ran successfully, or `false` (or a falsy value) if the record was skipped/ineligible. Errors and Exceptions will be caught by Scripterator and tabulated/output.
78
+
79
+ Optional:
80
+
81
+ - `model`: code with which model should be loaded, e.g., `model { User.includes(:profile, :roles) }`; if this block is not supplied, the model class is inferred from the `for_each_*` block, e.g., `for_each_post_comment` will cause the model `PostComment` to be loaded
82
+ - `before`: code to run before iteration begins
83
+ - `after`: code to run after iteration finishes
84
+
85
+ Environment variable options:
86
+
87
+ - `START`: first model ID to scripterate
88
+ - `END`: last model ID to scripterate
89
+ - `REDIS_EXPIRATION`: amount of time (in seconds) before Redis result sets (checked IDs and failed IDs) are expired
90
+
91
+ Either a starting or an ending ID must be provided.
92
+
93
+ ## Configuration
94
+
95
+ Within an optional Rails initializer, configure Scripterator further as follows (`config/initializers/scripterator.rb`):
96
+
97
+ ```ruby
98
+ Scripterator.configure do |config|
99
+ # alternate Redis instance
100
+ config.redis = MyRedis.new
101
+
102
+ # turn off Redis
103
+ config.redis = nil
104
+
105
+ # change default Redis set expiration time
106
+ config.redis_expiration = 5.days
107
+
108
+ # set redis_expiration to 0 to turn off expiration
109
+ config.redis_expiration = 0
110
+ end
111
+ ```
112
+
113
+ ## Running tests
114
+
115
+ $ bundle exec rspec
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create new Pull Request
@@ -0,0 +1,33 @@
1
+ require "redis" unless defined? Redis
2
+
3
+ require "scripterator/version"
4
+ require "scripterator/configuration"
5
+ require "scripterator/runner"
6
+
7
+ module Scripterator
8
+ class << self
9
+ def configure
10
+ yield config
11
+ end
12
+
13
+ def config
14
+ @config ||= Scripterator::Configuration.new
15
+ end
16
+
17
+ def run(description, &block)
18
+ options = {}.tap do |o|
19
+ o[:start_id] = ENV['START'].try(:to_i)
20
+ o[:end_id] = ENV['END'].try(:to_i)
21
+ o[:redis_expiration] = ENV['REDIS_EXPIRATION'].try(:to_i) || config.redis_expiration
22
+ end
23
+
24
+ Runner.new(description, &block).run(options)
25
+ end
26
+
27
+ %w(already_run_for? checked_ids failed_ids).each do |runner_method|
28
+ define_method(runner_method) do |description, *args|
29
+ Runner.new(description).send(runner_method, *args)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module Scripterator
2
+ class Configuration
3
+ attr_accessor :redis_expiration
4
+ attr_reader :redis
5
+
6
+ # set config.redis = nil to use NilRedis implementation
7
+ def redis=(r)
8
+ @redis = r || Scripterator::NilRedis.new
9
+ end
10
+ end
11
+
12
+ class NilRedis
13
+ def smembers(*args)
14
+ []
15
+ end
16
+
17
+ %w(expire sadd sismember).each do |redis_method|
18
+ define_method(redis_method) { |*args| nil }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,130 @@
1
+ require 'forwardable'
2
+ require_relative 'script_redis'
3
+
4
+ module Scripterator
5
+ class Runner
6
+ extend Forwardable
7
+ def_delegators :script_redis,
8
+ :already_run_for?, :checked_ids, :expire_redis_sets, :failed_ids,
9
+ :mark_as_failed_for, :mark_as_run_for, :script_key
10
+
11
+ def initialize(description, &config_block)
12
+ @script_description = description
13
+
14
+ self.instance_eval(&config_block) if config_block
15
+
16
+ @model ||= Proc.new { eval("#{@inferred_model_name}") } # constantize
17
+ end
18
+
19
+ def run(options = {})
20
+ unless options[:start_id] || options[:end_id]
21
+ raise 'You must provide either a start ID or end ID'
22
+ end
23
+ @start_id = options[:start_id] || 1
24
+ @end_id = options[:end_id]
25
+ @redis_expiration = options[:redis_expiration]
26
+ @output_stream = options[:output_stream] || $stdout
27
+
28
+ raise 'No per_record code defined' unless @per_record
29
+
30
+ init_vars
31
+ run_blocks
32
+ output_stats
33
+ end
34
+
35
+ %w(model before per_record after).each do |callback|
36
+ define_method callback do |&block|
37
+ instance_variable_set "@#{callback}", block
38
+ end
39
+ end
40
+
41
+ def method_missing(method_name, *args, &block)
42
+ if model_name = /for_each_(.+)/.match(method_name)[1]
43
+ @per_record = block
44
+ @inferred_model_name = model_name.split('_').map(&:capitalize).join
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def init_vars
53
+ @success_count = 0
54
+ @total_checked = 0
55
+ @already_done = 0
56
+ @errors = []
57
+ end
58
+
59
+ def fetch_record(id)
60
+ model_finder.find_by_id(id)
61
+ end
62
+
63
+ def model_finder
64
+ @model_finder ||= self.instance_eval(&@model)
65
+ end
66
+
67
+ def output_progress
68
+ if (@total_checked + @already_done) % 10000 == 0
69
+ output "#{Time.now}: Checked #{@total_checked} rows, #{@success_count} migrated."
70
+ end
71
+ end
72
+
73
+ def output_stats
74
+ output "Total rows migrated: #{@success_count} / #{@total_checked}"
75
+ output "#{@already_done} rows previously migrated and skipped"
76
+ output "#{@errors.count} errors"
77
+ if @errors.count > 0 && !failed_ids.empty?
78
+ output " Retrieve failed IDs with redis: SMEMBERS #{script_key(:failed)}"
79
+ end
80
+ end
81
+
82
+ def output(*args)
83
+ @output_stream.puts(*args)
84
+ end
85
+
86
+ def run_blocks
87
+ self.instance_eval(&@before) if @before
88
+
89
+ output "Starting at #{Time.now}..."
90
+ run_loop
91
+ output 'done'
92
+ output "Finished at #{Time.now}...\n\n"
93
+
94
+ self.instance_eval(&@after) if @after
95
+ end
96
+
97
+ def run_loop
98
+ if @end_id
99
+ (@start_id..@end_id).each { |id| transform_one_record(fetch_record(id)) }
100
+ else
101
+ model_finder.find_each(start: @start_id) { |record| transform_one_record(record) }
102
+ end
103
+
104
+ expire_redis_sets
105
+ end
106
+
107
+ def transform_one_record(record)
108
+ return if record.nil?
109
+
110
+ output_progress
111
+
112
+ if already_run_for? record.id
113
+ @already_done += 1
114
+ else
115
+ mark_as_run_for record.id
116
+ @total_checked += 1
117
+ @success_count += 1 if self.instance_exec record, &@per_record
118
+ end
119
+ rescue
120
+ errmsg = "Record #{record.id}: #{$!}"
121
+ output "Error: #{errmsg}"
122
+ @errors << errmsg
123
+ mark_as_failed_for record.id
124
+ end
125
+
126
+ def script_redis
127
+ @script_redis ||= ScriptRedis.new(@script_description, redis_expiration: @redis_expiration)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,46 @@
1
+ module Scripterator
2
+ class ScriptRedis
3
+ DEFAULT_EXPIRATION = 3 * 30 * 24 * 60 * 60 # 3 months
4
+
5
+ def initialize(script_description, options = {})
6
+ @key_prefix = "one_timer_script:#{script_description.downcase.split.join('_')}"
7
+ @redis_expiration = options[:redis_expiration] || DEFAULT_EXPIRATION
8
+ end
9
+
10
+ def checked_ids
11
+ redis.smembers(script_key(:checked)).map &:to_i
12
+ end
13
+
14
+ def failed_ids
15
+ redis.smembers(script_key(:failed)).map &:to_i
16
+ end
17
+
18
+ def already_run_for?(id)
19
+ redis.sismember script_key(:checked), id
20
+ end
21
+
22
+ def expire_redis_sets
23
+ unless @redis_expiration <= 0
24
+ %w(checked failed).each { |set| redis.expire script_key(set), @redis_expiration }
25
+ end
26
+ end
27
+
28
+ def mark_as_failed_for(id)
29
+ redis.sadd script_key(:failed), id
30
+ end
31
+
32
+ def mark_as_run_for(id)
33
+ redis.sadd script_key(:checked), id
34
+ end
35
+
36
+ def script_key(set_name)
37
+ "#{@key_prefix}:#{set_name}"
38
+ end
39
+
40
+ private
41
+
42
+ def redis
43
+ @redis ||= Scripterator.config.redis || Redis.new
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Scripterator
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/scripterator/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ted Dumitrescu"]
6
+ gem.email = ["ted@lumoslabs.com"]
7
+ gem.description = %q{Script iterator for ActiveRecord models}
8
+ gem.summary = %q{DSL for running operations on each of a set of models}
9
+ gem.homepage = "http://lumosity.com"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(spec)/})
14
+ gem.name = "scripterator"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Scripterator::VERSION
17
+
18
+ gem.add_dependency "activerecord", "~> 3.0"
19
+ gem.add_dependency "redis", "~> 3.0"
20
+
21
+ gem.add_development_dependency "fakeredis", "~> 0.4"
22
+ gem.add_development_dependency "rspec", "~> 2.13"
23
+ gem.add_development_dependency "sqlite3", "~> 1.3.8"
24
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+
3
+ describe Scripterator::Runner do
4
+ let(:runner) { Scripterator::Runner.new(description, &awesome_script) }
5
+ let(:description) { 'Convert widgets to eggplant parmigiana' }
6
+ let(:options) { {start_id: start_id, output_stream: StringIO.new} }
7
+ let(:start_id) { 1 }
8
+
9
+ let(:awesome_script) do
10
+ Proc.new do
11
+ before { Widget.before_stuff }
12
+ for_each_widget { |widget| Widget.transform_a_widget(widget) }
13
+ end
14
+ end
15
+
16
+ subject { runner.run(options) }
17
+
18
+ shared_examples_for 'raises an error' do
19
+ specify do
20
+ expect { subject }.to raise_error
21
+ end
22
+ end
23
+
24
+ it 'infers the model from the for_each block' do
25
+ runner.send(:model_finder).should == Widget
26
+ end
27
+
28
+ context 'when a model block is defined' do
29
+ let(:awesome_script) do
30
+ Proc.new do
31
+ model { Widget.where(name: 'bla') }
32
+ before { Widget.before_stuff }
33
+ for_each_widget { |widget| Widget.transform_a_widget(widget) }
34
+ end
35
+ end
36
+ let!(:widget1) { Widget.create(name: 'foo') }
37
+ let!(:widget2) { Widget.create(name: 'bla') }
38
+
39
+ it 'uses the given model finder code' do
40
+ Widget.should_receive(:transform_a_widget).once.with(widget2)
41
+ Widget.should_not_receive(:transform_a_widget).with(widget1)
42
+ subject
43
+ end
44
+ end
45
+
46
+ context 'when no start or end ID is passed' do
47
+ let(:options) { {} }
48
+
49
+ it_behaves_like 'raises an error'
50
+ end
51
+
52
+ context 'when no per-record block is defined' do
53
+ let(:awesome_script) do
54
+ Proc.new do
55
+ model { Widget }
56
+ before { Widget.before_stuff }
57
+ end
58
+ end
59
+
60
+ it_behaves_like 'raises an error'
61
+
62
+ it 'does not run any other given blocks' do
63
+ Widget.should_not_receive :before_stuff
64
+ subject rescue nil
65
+ end
66
+ end
67
+
68
+ context 'when there are no records for the specified model' do
69
+ it 'does not run the per-record block' do
70
+ Widget.should_not_receive :transform_a_widget
71
+ subject
72
+ end
73
+ end
74
+
75
+ context 'when there are records for the specified model' do
76
+ let(:num_widgets) { 3 }
77
+
78
+ before { num_widgets.times { Widget.create! } }
79
+
80
+ it 'runs the given script blocks' do
81
+ Widget.should_receive :before_stuff
82
+ Widget.should_receive(:transform_a_widget).exactly(num_widgets).times
83
+ subject
84
+ end
85
+
86
+ context 'when not all records are checked' do
87
+ let(:start_id) { Widget.last.id }
88
+
89
+ it 'marks only the checked IDs as checked' do
90
+ subject
91
+ Scripterator.already_run_for?(description, Widget.first.id).should be_false
92
+ Scripterator.checked_ids(description).should_not include Widget.first.id
93
+ Scripterator.already_run_for?(description, Widget.last.id).should be_true
94
+ Scripterator.checked_ids(description).should include Widget.last.id
95
+ end
96
+ end
97
+
98
+ context 'when some records have already been checked' do
99
+ let(:checked_ids) { [Widget.first.id] }
100
+
101
+ before do
102
+ Scripterator.stub(checked_ids: checked_ids)
103
+ Scripterator::ScriptRedis.any_instance.stub(already_run_for?: false)
104
+ Scripterator::ScriptRedis.any_instance.stub(:already_run_for?).with(Widget.first.id).and_return(true)
105
+ end
106
+
107
+ it 'only runs the per-record code for unchecked records' do
108
+ Widget.should_receive(:transform_a_widget).exactly(num_widgets - 1).times
109
+ subject
110
+ end
111
+ end
112
+
113
+ context 'when the code for some records fails' do
114
+ before do
115
+ Widget.stub :transform_a_widget do |widget|
116
+ raise 'Last widget expl0de' if widget.id == Widget.last.id
117
+ true
118
+ end
119
+ end
120
+
121
+ it 'marks only the failed IDs as failed' do
122
+ subject
123
+ Scripterator.failed_ids(description).should_not include Widget.first.id
124
+ Scripterator.failed_ids(description).should include Widget.last.id
125
+ end
126
+ end
127
+
128
+ context 'when Redis client is set to nil' do
129
+ before { Scripterator.configure { |config| config.redis = nil } }
130
+ after { Scripterator.instance_variable_set(:@config, nil) }
131
+
132
+ it 'runs without Redis' do
133
+ expect { subject }.not_to raise_error
134
+ Scripterator.checked_ids(description).should be_empty
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,28 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+
6
+ require 'fakeredis/rspec'
7
+ require 'scripterator'
8
+ require 'support/widget'
9
+
10
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
11
+ RSpec.configure do |config|
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ config.run_all_when_everything_filtered = true
14
+ config.filter_run :focus
15
+
16
+ # Run specs in random order to surface order dependencies. If you find an
17
+ # order dependency and want to debug it, you can fix the order by providing
18
+ # the seed, which is printed after each run.
19
+ # --seed 1234
20
+ config.order = 'random'
21
+
22
+ config.around do |example|
23
+ ActiveRecord::Base.transaction do
24
+ example.run
25
+ raise ActiveRecord::Rollback
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
4
+
5
+ ActiveRecord::Migration.create_table :widgets do |t|
6
+ t.string :name
7
+ t.timestamps
8
+ end
9
+
10
+ class Widget < ActiveRecord::Base
11
+ # some dummy methods for setting message expectations in specs
12
+ def self.before_stuff; end
13
+ def self.transform_a_widget(widget); true; end
14
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scripterator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ted Dumitrescu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fakeredis
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.8
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.8
83
+ description: Script iterator for ActiveRecord models
84
+ email:
85
+ - ted@lumoslabs.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .rspec
92
+ - Gemfile
93
+ - LICENSE
94
+ - README.md
95
+ - lib/scripterator.rb
96
+ - lib/scripterator/configuration.rb
97
+ - lib/scripterator/runner.rb
98
+ - lib/scripterator/script_redis.rb
99
+ - lib/scripterator/version.rb
100
+ - scripterator.gemspec
101
+ - spec/runner_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/widget.rb
104
+ homepage: http://lumosity.com
105
+ licenses: []
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.1.4
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: DSL for running operations on each of a set of models
127
+ test_files:
128
+ - spec/runner_spec.rb
129
+ - spec/spec_helper.rb
130
+ - spec/support/widget.rb
131
+ has_rdoc: