rubyrep 1.1.2 → 1.2.0

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/.gemtest ADDED
File without changes
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 1.2.0 2011-03-07
2
+
3
+ * Feature: compatibility with Rails 3
4
+ * Feature: do not replicate record updates that didn't change any fields (props to daudo)
5
+ * Bug fix: reducing deadlock problems (props to gtanzillo)
6
+ * Bug fix: adding missing schema prefix in PostgreSQL triggers
7
+ * Bug fix: scans / syncs fail due to incorrect handling of case sensitivity of string primary key columns
8
+ * Bug fix: reducing risk of foreign key conflicts during replication (props for root cause analysis to TonyB)
9
+
1
10
  == 1.1.2 2009-05-10
2
11
 
3
12
  * Bug fix: escape primary keys in replication triggers
data/bin/rubyrep CHANGED
File without changes
data/config/hoe.rb CHANGED
@@ -45,25 +45,23 @@ end
45
45
 
46
46
  # Generate all the Rake tasks
47
47
  # Run 'rake -T' to see list of generated tasks (from gem root directory)
48
- hoe = Hoe.new(GEM_NAME, VERS) do |p|
49
- p.author = AUTHOR
50
- p.description = DESCRIPTION
51
- p.email = EMAIL
52
- p.summary = DESCRIPTION
53
- p.url = HOMEPATH
54
- p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
55
- p.test_globs = ["test/**/test_*.rb"]
56
- p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
57
-
48
+ hoe = Hoe.spec(GEM_NAME) do
49
+ self.version = VERS
50
+ developer AUTHOR, EMAIL
51
+ description = DESCRIPTION
52
+ summary = DESCRIPTION
53
+ url = HOMEPATH
54
+ rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
55
+ test_globs = ["test/**/test_*.rb"]
56
+ clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
57
+
58
58
  # == Optional
59
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
60
- #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
61
- p.extra_deps = [
62
- ['activesupport', '>= 2.3.5'],
63
- ['activerecord' , '>= 2.3.5']
64
- ]
59
+ changes = paragraphs_of("History.txt", 0..1).join("\\n\\n")
60
+ #extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
61
+ extra_deps << ['activesupport', '>= 3.0.5']
62
+ extra_deps << ['activerecord' , '>= 3.0.5']
65
63
 
66
- #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+ #spec_extras = {} # A hash of extra values to set in the gemspec.
67
65
 
68
66
  end
69
67
 
data/lib/rubyrep.rb CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + "/rubyrep"
4
4
  require 'rubygems'
5
5
  require 'yaml'
6
6
 
7
- gem 'activerecord', '>= 2.2.2'
7
+ gem 'activerecord', '>= 3.0.5'
8
8
  require 'active_record'
9
9
 
10
10
  require 'version'
@@ -1,3 +1,8 @@
1
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+ # The current log subscriber
3
+ attr_accessor :log_subscriber
4
+ end
5
+
1
6
  class ActiveRecord::ConnectionAdapters::Column
2
7
  # Bug in ActiveRecord parsing of PostgreSQL timestamps with microseconds:
3
8
  # Certain values are incorrectly rounded, thus ending up with timestamps
@@ -107,9 +112,17 @@ module RR
107
112
  if config[:logger].respond_to?(:debug)
108
113
  logger = config[:logger]
109
114
  else
110
- logger = Logger.new(config[:logger])
115
+ logger = ActiveSupport::BufferedLogger.new(config[:logger])
111
116
  end
112
117
  db_connection.instance_variable_set :@logger, logger
118
+ if ActiveSupport.const_defined?(:Notifications)
119
+ connection_object_id = db_connection.object_id
120
+ db_connection.log_subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, payload|
121
+ if payload[:connection_id] == connection_object_id and logger.debug?
122
+ logger.debug payload[:sql].squeeze(" ")
123
+ end
124
+ end
125
+ end
113
126
  end
114
127
  end
115
128
 
@@ -58,23 +58,8 @@ module RR
58
58
  end
59
59
  end
60
60
 
61
- # activerecord-jdbc-adapter 0.9.1 (7b3f3eca08149567070837fad63696052dc36cd6)
62
- # improves SQLite binary support by overwriting the global string_to_binary
63
- # methods.
64
- # This appears to break binary support for MySQL.
65
- # And here comes the monkey patch to revert it again...
66
- require 'active_record/connection_adapters/jdbc_adapter_spec'
67
- require 'jdbc_adapter/jdbc_sqlite3'
68
- module ::ActiveRecord
69
- module ConnectionAdapters
70
- class JdbcColumn < Column
71
- def self.string_to_binary(value)
72
- value
73
- end
74
-
75
- def self.binary_to_string(value)
76
- value
77
- end
78
- end
79
- end
61
+ require 'activerecord-jdbc-adapter'
62
+ if ArJdbc.const_defined?(:PostgreSQL)
63
+ ArJdbc::PostgreSQL::RecordNotUnique = ActiveRecord::RecordNotUnique unless ArJdbc::PostgreSQL.const_defined?(:RecordNotUnique)
64
+ ArJdbc::PostgreSQL::InvalidForeignKey = ActiveRecord::InvalidForeignKey unless ArJdbc::PostgreSQL.const_defined?(:InvalidForeignKey)
80
65
  end
@@ -239,6 +239,11 @@ module RR
239
239
  end
240
240
  cursors.clear
241
241
 
242
+ if connection.log_subscriber
243
+ ActiveSupport::Notifications.notifier.unsubscribe connection.log_subscriber
244
+ connection.log_subscriber = nil
245
+ end
246
+
242
247
  self.connection.disconnect!
243
248
  end
244
249
 
@@ -19,6 +19,10 @@ module RR
19
19
  # * :+no_diff+: changes in both databases constitute no difference
20
20
  attr_accessor :type
21
21
 
22
+ # Is set to +true+ if first replication attempt failed but it should be tried again later
23
+ attr_accessor :second_chance
24
+ alias_method :second_chance?, :second_chance
25
+
22
26
  # A hash with keys :+left+ and / or :+right+.
23
27
  # Hash values are LoggedChange instances.
24
28
  def changes
@@ -45,13 +45,25 @@ module RR
45
45
  activity_check = ""
46
46
  if params[:exclude_rr_activity] then
47
47
  activity_check = <<-end_sql
48
- PERFORM ACTIVE FROM #{params[:activity_table]};
48
+ PERFORM ACTIVE FROM #{schema_prefix}#{params[:activity_table]};
49
49
  IF FOUND THEN
50
50
  RETURN NULL;
51
51
  END IF;
52
52
  end_sql
53
53
  end
54
54
 
55
+ version_string = select_value("select version();")
56
+ version = version_string.gsub(/^\s*postgresql\s*([0-9.]+).*$/i, '\1')
57
+ if version >= '8.4'
58
+ modification_check = <<-end_sql
59
+ IF NEW IS NOT DISTINCT FROM OLD THEN
60
+ RETURN NULL;
61
+ END IF;
62
+ end_sql
63
+ else
64
+ modification_check = ""
65
+ end
66
+
55
67
  # now create the trigger
56
68
  execute(<<-end_sql)
57
69
  CREATE OR REPLACE FUNCTION "#{params[:trigger_name]}"() RETURNS TRIGGER AS $change_trigger$
@@ -61,6 +73,7 @@ module RR
61
73
  INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_type, change_time)
62
74
  SELECT '#{params[:table]}', #{key_clause('OLD', params)}, 'D', now();
63
75
  ELSIF (TG_OP = 'UPDATE') THEN
76
+ #{modification_check}
64
77
  INSERT INTO #{schema_prefix}#{params[:log_table]}(change_table, change_key, change_new_key, change_type, change_time)
65
78
  SELECT '#{params[:table]}', #{key_clause('OLD', params)}, #{key_clause('NEW', params)}, 'U', now();
66
79
  ELSIF (TG_OP = 'INSERT') THEN
@@ -11,6 +11,11 @@ module RR
11
11
  # The current TaskSweeper
12
12
  attr_accessor :sweeper
13
13
 
14
+ # An array of ReplicationDifference which originally failed replication but should be tried one more time
15
+ def second_chancers
16
+ @second_chancers ||= []
17
+ end
18
+
14
19
  # Returns the current ReplicationHelper; creates it if necessary
15
20
  def helper
16
21
  @helper ||= ReplicationHelper.new(self)
@@ -39,6 +44,20 @@ module RR
39
44
  end
40
45
  end
41
46
 
47
+ # Returns the next available ReplicationDifference.
48
+ # (Either new unprocessed differences or if not available, the first available 'second chancer'.)
49
+ #
50
+ def load_difference
51
+ @loaders ||= LoggedChangeLoaders.new(session)
52
+ @loaders.update # ensure the cache of change log records is up-to-date
53
+ diff = ReplicationDifference.new @loaders
54
+ diff.load
55
+ unless diff.loaded? or second_chancers.empty?
56
+ diff = second_chancers.shift
57
+ end
58
+ diff
59
+ end
60
+
42
61
  # Executes the replication run.
43
62
  def run
44
63
  return unless [:left, :right].any? do |database|
@@ -57,29 +76,36 @@ module RR
57
76
  # Check for this and if timed out, return (silently).
58
77
  return if sweeper.terminated?
59
78
 
60
- loaders = LoggedChangeLoaders.new(session)
61
-
62
79
  success = false
63
80
  begin
64
81
  replicator # ensure that replicator is created and has chance to validate settings
65
82
 
66
83
  loop do
67
84
  begin
68
- loaders.update # ensure the cache of change log records is up-to-date
69
- diff = ReplicationDifference.new loaders
70
- diff.load
85
+ diff = load_difference
71
86
  break unless diff.loaded?
72
87
  break if sweeper.terminated?
73
88
  if diff.type != :no_diff and not event_filtered?(diff)
74
89
  replicator.replicate_difference diff
75
90
  end
76
91
  rescue Exception => e
77
- begin
78
- helper.log_replication_outcome diff, e.message,
79
- e.class.to_s + "\n" + e.backtrace.join("\n")
80
- rescue Exception => _
81
- # if logging to database itself fails, re-raise the original exception
82
- raise e
92
+ if e.message =~ /violates foreign key constraint|foreign key constraint fails/i and !diff.second_chance?
93
+ # Note:
94
+ # Identifying the foreign key constraint violation via regular expression is
95
+ # database dependent and *dirty*.
96
+ # It would be better to use the ActiveRecord #translate_exception mechanism.
97
+ # However as per version 3.0.5 this doesn't work yet properly.
98
+
99
+ diff.second_chance = true
100
+ second_chancers << diff
101
+ else
102
+ begin
103
+ helper.log_replication_outcome diff, e.message,
104
+ e.class.to_s + "\n" + e.backtrace.join("\n")
105
+ rescue Exception => _
106
+ # if logging to database itself fails, re-raise the original exception
107
+ raise e
108
+ end
83
109
  end
84
110
  end
85
111
  end
@@ -2,6 +2,12 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
 
3
3
  require 'optparse'
4
4
  require 'thread'
5
+ require 'monitor'
6
+
7
+ class Monitor
8
+ alias lock mon_enter
9
+ alias unlock mon_exit
10
+ end
5
11
 
6
12
  module RR
7
13
  # This class implements the functionality of the 'replicate' command.
@@ -94,7 +100,7 @@ EOS
94
100
  # Initializes the waiter thread used for replication pauses and processing
95
101
  # the process TERM signal.
96
102
  def init_waiter
97
- @termination_mutex = Mutex.new
103
+ @termination_mutex = Monitor.new
98
104
  @termination_mutex.lock
99
105
  @waiter_thread ||= Thread.new {@termination_mutex.lock; self.termination_requested = true}
100
106
  %w(TERM INT).each do |signal|
@@ -19,7 +19,15 @@ module RR
19
19
  return 1 unless left_row
20
20
  rank = 0
21
21
  primary_key_names.any? do |key|
22
- rank = left_row[key] <=> right_row[key]
22
+ if left_row[key].kind_of?(String)
23
+ # When databases order strings, then 'a' < 'A' while for Ruby 'A' < 'a'
24
+ # ==> Use a combination of case sensitive and case insensitive comparing to
25
+ # reproduce the database behaviour.
26
+ rank = left_row[key].casecmp(right_row[key]) # deal with 'a' to 'B' comparisons
27
+ rank = -(left_row[key] <=> right_row[key]) if rank == 0 # deal with 'a' to 'A' comparisons
28
+ else
29
+ rank = left_row[key] <=> right_row[key]
30
+ end
23
31
  rank != 0
24
32
  end
25
33
  rank
@@ -1,8 +1,8 @@
1
1
  module RR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 1
5
- TINY = 2
4
+ MINOR = 2
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,3 +1,4 @@
1
+
1
2
  require 'rake'
2
3
  require 'benchmark'
3
4
 
@@ -136,7 +137,7 @@ def populate_rep_data
136
137
  # Updating progress bar
137
138
  progress_bar.step
138
139
 
139
- database = [:left, :right].rand
140
+ database = [:left, :right][rand(2)]
140
141
 
141
142
  case rand(100)
142
143
  when 0...BIG_REP_INSERT
@@ -147,7 +148,7 @@ def populate_rep_data
147
148
  next_id += 1
148
149
  session.send(database).insert_record 'big_rep', attributes
149
150
  when BIG_REP_INSERT...BIG_REP_UPDATE
150
- id = all_ids[database].rand
151
+ id = all_ids[database][rand(all_ids[database].size)]
151
152
  attributes = session.send(database).select_one("select * from big_rep where id = '#{id}'")
152
153
  column = number_columns[rand(number_columns.size)]
153
154
  attributes[column] = rand(1000)
@@ -10,9 +10,13 @@ describe ConnectionExtenders do
10
10
  it "db_connect should install the already created logger" do
11
11
  configuration = deep_copy(Initializer.configuration)
12
12
  io = StringIO.new
13
- logger = Logger.new(io)
13
+ logger = ActiveSupport::BufferedLogger.new(io)
14
14
  configuration.left[:logger] = logger
15
15
  session = Session.new configuration
16
+
17
+ session.left.connection.instance_eval {@logger}.should == logger
18
+ session.right.connection.instance_eval {@logger}.should_not == logger
19
+
16
20
  session.left.select_one "select 'left_query'"
17
21
  session.right.select_one "select 'right_query'"
18
22
 
@@ -9,7 +9,7 @@ describe ProxyConnection do
9
9
  end
10
10
 
11
11
  it "initialize should connect to the database" do
12
- @connection.connection.active?.should == true
12
+ (!!@connection.connection.active?).should == true
13
13
  end
14
14
 
15
15
  it "initialize should store the configuratin" do
@@ -17,9 +17,21 @@ describe ProxyConnection do
17
17
  end
18
18
 
19
19
  it "destroy should disconnect from the database" do
20
+ if ActiveSupport.const_defined?(:Notifications)
21
+ ConnectionExtenders::install_logger @connection.connection, :logger => StringIO.new
22
+ log_subscriber = @connection.connection.log_subscriber
23
+
24
+ ActiveSupport::Notifications.notifier.listeners_for("sql.active_record").should include(log_subscriber)
25
+ end
26
+
20
27
  @connection.destroy
21
28
 
22
- @connection.connection.active?.should == false
29
+ if ActiveSupport.const_defined?(:Notifications)
30
+ ActiveSupport::Notifications.notifier.listeners_for("sql.active_record").should_not include(log_subscriber)
31
+ @connection.connection.log_subscriber.should be_nil
32
+ end
33
+
34
+ (!!@connection.connection.active?).should == false
23
35
  end
24
36
 
25
37
  it "cursors should return the current cursor hash or an empty hash if nil" do
@@ -135,6 +135,68 @@ describe ReplicationRun do
135
135
  end
136
136
  end
137
137
 
138
+ it "run should replication records with foreign key constraints" do
139
+ begin
140
+ config = deep_copy(standard_config)
141
+ config.options[:committer] = :never_commit
142
+
143
+ session = Session.new(config)
144
+
145
+ session.left.insert_record 'referencing_table', {
146
+ 'id' => '5',
147
+ }
148
+ session.left.insert_record 'rr_pending_changes', {
149
+ 'change_table' => 'referencing_table',
150
+ 'change_key' => 'id|5',
151
+ 'change_type' => 'I',
152
+ 'change_time' => Time.now
153
+ }
154
+
155
+ session.left.insert_record 'referenced_table2', {
156
+ 'id' => '6',
157
+ }
158
+ session.left.insert_record 'rr_pending_changes', {
159
+ 'change_table' => 'referenced_table2',
160
+ 'change_key' => 'id|6',
161
+ 'change_type' => 'I',
162
+ 'change_time' => Time.now
163
+ }
164
+
165
+ session.left.update_record 'referencing_table', {
166
+ 'id' => 5,
167
+ 'third_fk' => '6'
168
+ }
169
+ session.left.insert_record 'rr_pending_changes', {
170
+ 'change_table' => 'referencing_table',
171
+ 'change_key' => 'id|5',
172
+ 'change_new_key' => 'id|5',
173
+ 'change_type' => 'U',
174
+ 'change_time' => Time.now
175
+ }
176
+
177
+ run = ReplicationRun.new session, TaskSweeper.new(1)
178
+ run.run
179
+
180
+ session.right.select_record(:table => "referencing_table", :from => {'id' => 5}).should == {
181
+ 'id' => 5,
182
+ 'first_fk' => nil,
183
+ 'second_fk' => nil,
184
+ 'third_fk' => 6
185
+ }
186
+ ensure
187
+ Committers::NeverCommitter.rollback_current_session
188
+ if session
189
+ session.left.execute "delete from referencing_table where id = 5"
190
+ session.left.execute "delete from referenced_table2 where id = 6"
191
+
192
+ session.right.execute "delete from referencing_table where id = 5"
193
+ session.right.execute "delete from referenced_table2 where id = 6"
194
+
195
+ session.left.execute "delete from rr_pending_changes"
196
+ end
197
+ end
198
+ end
199
+
138
200
  it "run should not replicate filtered changes" do
139
201
  begin
140
202
  config = deep_copy(standard_config)
@@ -13,6 +13,13 @@ describe TableScanHelper do
13
13
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 1}).should == 0
14
14
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 2}).should == -1
15
15
  @scan.rank_rows({'first_id' => 2, 'second_id' => 1}, {'first_id' => 1, 'second_id' => 1}).should == 1
16
+
17
+ # should rank strings according to database logic ('a' < 'A')
18
+ # instead of the Ruby logic (which is the other way round)
19
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'B', 'second_id' => 1}).should == -1
20
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'A', 'second_id' => 1}).should == -1
21
+ @scan.rank_rows({'first_id' => 'a', 'second_id' => 1}, {'first_id' => 'a', 'second_id' => 1}).should == 0
22
+
16
23
  lambda {@scan.rank_rows(nil,nil)}.should raise_error(RuntimeError, 'At least one of left_row and right_row must not be nil!')
17
24
  @scan.rank_rows(nil, {'first_id' => 1, 'second_id' => 1}).should == 1
18
25
  @scan.rank_rows({'first_id' => 1, 'second_id' => 1}, nil).should == -1
data/tasks/database.rake CHANGED
@@ -18,7 +18,7 @@ def create_database(config)
18
18
  @charset = ENV['CHARSET'] || 'utf8'
19
19
  @collation = ENV['COLLATION'] || 'utf8_general_ci'
20
20
  begin
21
- connection = RR::ConnectionExtenders.db_connect(config.merge({'database' => nil}))
21
+ connection = RR::ConnectionExtenders.db_connect(config.merge({:database => nil}))
22
22
  connection.create_database(config[:database], {:charset => @charset, :collation => @collation})
23
23
  RR::ConnectionExtenders.db_connect(config)
24
24
  rescue
data/tasks/rspec.rake CHANGED
@@ -41,18 +41,14 @@ namespace :spec do
41
41
  desc "Run the specs for all supported databases"
42
42
  task :all_dbs do
43
43
  [:postgres, :mysql].each do |test_db|
44
- puts "Running specs for #{test_db.id2name}"
45
- ENV['RR_TEST_DB'] = test_db.id2name
46
- system "spec spec"
44
+ puts "Running specs for #{test_db}"
45
+ system "bash -c 'RR_TEST_DB=#{test_db} spec spec'"
47
46
  end
48
47
  end
49
48
 
50
49
  desc "Run the specs for all supported databases and ruby platforms"
51
50
  task :all_rubies do
52
- puts "Running spec:all_dbs in standard ruby"
53
- system "rake spec:all_dbs"
54
- puts "Running spec:all_dbs in jruby"
55
- system "export PATH=#{JRUBY_HOME}/bin:$PATH; rake spec:all_dbs"
51
+ system %(rvm exec bash -c 'for db in postgres mysql; do echo "`rvm current` - $db:"; RR_TEST_DB=$db spec spec; done')
56
52
  end
57
53
 
58
54
  begin
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyrep
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 31
5
+ prerelease:
5
6
  segments:
6
7
  - 1
7
- - 1
8
8
  - 2
9
- version: 1.1.2
9
+ - 0
10
+ version: 1.2.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Arndt Lehmann
@@ -14,53 +15,60 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-05-10 00:00:00 +09:00
18
+ date: 2011-03-07 00:00:00 +09:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: activesupport
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 13
27
30
  segments:
28
- - 2
29
31
  - 3
32
+ - 0
30
33
  - 5
31
- version: 2.3.5
34
+ version: 3.0.5
32
35
  type: :runtime
33
36
  version_requirements: *id001
34
37
  - !ruby/object:Gem::Dependency
35
38
  name: activerecord
36
39
  prerelease: false
37
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
38
42
  requirements:
39
43
  - - ">="
40
44
  - !ruby/object:Gem::Version
45
+ hash: 13
41
46
  segments:
42
- - 2
43
47
  - 3
48
+ - 0
44
49
  - 5
45
- version: 2.3.5
50
+ version: 3.0.5
46
51
  type: :runtime
47
52
  version_requirements: *id002
48
53
  - !ruby/object:Gem::Dependency
49
54
  name: hoe
50
55
  prerelease: false
51
56
  requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
52
58
  requirements:
53
59
  - - ">="
54
60
  - !ruby/object:Gem::Version
61
+ hash: 41
55
62
  segments:
56
63
  - 2
57
- - 4
58
- - 0
59
- version: 2.4.0
64
+ - 9
65
+ - 1
66
+ version: 2.9.1
60
67
  type: :development
61
68
  version_requirements: *id003
62
- description: Asynchronous master-master replication of relational databases.
63
- email: mail@arndtlehman.com
69
+ description: ""
70
+ email:
71
+ - mail@arndtlehman.com
64
72
  executables:
65
73
  - rubyrep
66
74
  extensions: []
@@ -222,8 +230,9 @@ files:
222
230
  - tasks/stats.rake
223
231
  - tasks/task_helper.rb
224
232
  - tasks/website.rake
233
+ - .gemtest
225
234
  has_rdoc: true
226
- homepage: http://rubyrep.rubyforge.org
235
+ homepage:
227
236
  licenses: []
228
237
 
229
238
  post_install_message:
@@ -233,25 +242,29 @@ rdoc_options:
233
242
  require_paths:
234
243
  - lib
235
244
  required_ruby_version: !ruby/object:Gem::Requirement
245
+ none: false
236
246
  requirements:
237
247
  - - ">="
238
248
  - !ruby/object:Gem::Version
249
+ hash: 3
239
250
  segments:
240
251
  - 0
241
252
  version: "0"
242
253
  required_rubygems_version: !ruby/object:Gem::Requirement
254
+ none: false
243
255
  requirements:
244
256
  - - ">="
245
257
  - !ruby/object:Gem::Version
258
+ hash: 3
246
259
  segments:
247
260
  - 0
248
261
  version: "0"
249
262
  requirements: []
250
263
 
251
264
  rubyforge_project: rubyrep
252
- rubygems_version: 1.3.6
265
+ rubygems_version: 1.5.2
253
266
  signing_key:
254
267
  specification_version: 3
255
- summary: Asynchronous master-master replication of relational databases.
268
+ summary: ""
256
269
  test_files: []
257
270