activerecord_worm_table 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.8.1
@@ -1,206 +1,2 @@
1
1
  require 'active_record'
2
-
3
- # implements a write-once-read-many table, wherein there is a
4
- # currently active version of a table, one or more historical
5
- # versions and a working version. modifications are made
6
- # by writing new data to the working version of the table
7
- # and then switching the active version to what was the working version.
8
- #
9
- # each version is a separate database table, and there is a
10
- # switch table with a single row which names the currently live
11
- # version table in the database
12
-
13
- module ActiveRecord
14
- module WormTable
15
- def self.included(mod)
16
- mod.instance_eval do
17
- class << self
18
- include ClassMethods
19
- end
20
- end
21
- end
22
-
23
- module ClassMethods
24
- ALPHABET = "abcdefghijklmnopqrstuvwxyz"
25
-
26
- def ClassMethods.included(mod)
27
- mod.instance_eval do
28
- alias_method :org_table_name, :table_name
29
- alias_method :table_name, :active_working_table_or_active_table_name
30
- end
31
- end
32
-
33
- # number of historical tables to keep around for posterity, or more likely
34
- # to ensure running transactions aren't taken down by advance_version
35
- # recreating a table. default 2
36
- def historical_version_count
37
- @historical_version_count || 2
38
- end
39
-
40
- # set the number of historical tables to keep around to ensure running
41
- # transactions aren't interrupted by truncating working tables. 2 is default
42
- def set_historical_version_count(count)
43
- @historical_version_count = count
44
- end
45
- alias :historical_version_count= :set_historical_version_count
46
-
47
- # hide the ActiveRecord::Base method, which redefines a table_name method,
48
- # and instead capture the given name as the base_table_name
49
- def set_table_name(name)
50
- @base_table_name = name
51
- end
52
- alias :table_name= :set_table_name
53
-
54
- def base_table_name
55
- if !@base_table_name
56
- @base_table_name = org_table_name
57
- # the original table_name method re-aliases itself !
58
- class << self
59
- alias_method :table_name, :active_working_table_or_active_table_name
60
- end
61
- end
62
- @base_table_name
63
- end
64
-
65
- # use schema of from table to recreate to table
66
- def dup_table_schema(from, to)
67
- connection.execute( "drop table if exists #{to}")
68
- ct = connection.select_one( "show create table #{from}")["Create Table"]
69
- ct_no_constraint_names = ct.gsub(/CONSTRAINT `[^`]*`/, "CONSTRAINT ``")
70
- i = 0
71
- ct_uniq_constraint_names = ct_no_constraint_names.gsub(/CONSTRAINT ``/) { |s| i+=1 ; "CONSTRAINT `#{to}_#{i}`" }
72
-
73
- new_ct = ct_uniq_constraint_names.gsub( /CREATE TABLE `#{from}`/, "CREATE TABLE `#{to}`")
74
- connection.execute(new_ct)
75
- end
76
-
77
- def ensure_version_table(name)
78
- if !connection.table_exists?(name) &&
79
- base_table_name!=name # don't execute ddl unless necessary
80
- dup_table_schema(base_table_name, name)
81
- end
82
- end
83
-
84
- # create a switch table of given name, if it doesn't already exist
85
- def create_switch_table(name)
86
- connection.execute( "create table if not exists #{name} (`current` varchar(255))" )
87
- end
88
-
89
- # create the switch table if it doesn't already exist. return the switch table name
90
- def ensure_switch_table
91
- stn = switch_table_name
92
- if !connection.table_exists?(stn) # don't execute any ddl code if we don't need to
93
- create_switch_table(stn)
94
- end
95
- stn
96
- end
97
-
98
- # name of the table with a row holding the active table name
99
- def switch_table_name
100
- base_table_name + "_switch"
101
- end
102
-
103
- # list of suffixed table names
104
- def suffixed_table_names
105
- suffixes = []
106
- (0...historical_version_count).each{ |i| suffixes << ALPHABET[i...i+1] }
107
- suffixes.map do |suffix|
108
- base_table_name + "_" + suffix
109
- end
110
- end
111
-
112
- # ordered vector of table version names, starting with base name
113
- def table_version_names
114
- [base_table_name] + suffixed_table_names
115
- end
116
-
117
- def default_active_table_name
118
- # no longer use a different table name for test environments...
119
- # it makes a mess with named scopes
120
- base_table_name
121
- end
122
-
123
- # name of the active table read direct from db
124
- def active_table_name
125
- st = switch_table_name
126
- begin
127
- connection.select_value( "select current from #{st}" )
128
- rescue
129
- end || default_active_table_name
130
- end
131
-
132
- # name of the working table
133
- def working_table_name
134
- atn = active_table_name
135
- tvn = table_version_names
136
- tvn[ (tvn.index(atn) + 1) % tvn.size ]
137
- end
138
-
139
- def ensure_all_tables
140
- suffixed_table_names.each do |table_name|
141
- ensure_version_table(table_name)
142
- end
143
- ensure_switch_table
144
- end
145
-
146
- # make working table active, then recreate new working table from base table schema
147
- def advance_version
148
- st = ensure_switch_table
149
-
150
- # want a transaction at least here [surround is ok too] so
151
- # there is never an empty switch table
152
- ActiveRecord::Base.transaction do
153
- wtn = working_table_name
154
- connection.execute( "delete from #{st}")
155
- connection.execute( "insert into #{st} values (\'#{wtn}\')")
156
- end
157
-
158
- # ensure the presence of the new active and working tables.
159
- # happens after the switch table update, since this may commit a surrounding
160
- # transaction in dbs with retarded non-transactional ddl like, oh i dunno, MyFuckingSQL
161
- ensure_version_table(active_table_name)
162
-
163
- # recreate the new working table from the base schema.
164
- new_wtn = working_table_name
165
- if new_wtn != base_table_name
166
- dup_table_schema(base_table_name, new_wtn)
167
- else
168
- connection.execute( "truncate table #{new_wtn}" )
169
- end
170
- end
171
-
172
- def thread_local_key_name
173
- "ActiveRecord::WormTable::" + self.to_s
174
- end
175
-
176
- def active_working_table_name
177
- Thread.current[thread_local_key_name]
178
- end
179
-
180
- def active_working_table_name=(name)
181
- Thread.current[thread_local_key_name] = name
182
- end
183
-
184
- # name of the active table, or the working table if inside a new_version block
185
- def active_working_table_or_active_table_name
186
- active_working_table_name || active_table_name
187
- end
188
-
189
- # make the working table temporarily active [ for this thread only ],
190
- # execute the block, and if completed without exception then
191
- # make the working table permanently active
192
- def new_version(&block)
193
- begin
194
- self.active_working_table_name = working_table_name
195
- ensure_version_table(working_table_name)
196
- connection.execute("truncate table #{working_table_name}")
197
- r = block.call
198
- advance_version
199
- r
200
- ensure
201
- self.active_working_table_name = nil
202
- end
203
- end
204
- end
205
- end
206
- end
2
+ require 'activerecord_worm_table/worm_table'
@@ -0,0 +1,204 @@
1
+ # implements a write-once-read-many table, wherein there is a
2
+ # currently active version of a table, one or more historical
3
+ # versions and a working version. modifications are made
4
+ # by writing new data to the working version of the table
5
+ # and then switching the active version to what was the working version.
6
+ #
7
+ # each version is a separate database table, and there is a
8
+ # switch table with a single row which names the currently live
9
+ # version table in the database
10
+
11
+ module ActiveRecord
12
+ module WormTable
13
+ def self.included(mod)
14
+ mod.instance_eval do
15
+ class << self
16
+ include ClassMethods
17
+ end
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ ALPHABET = "abcdefghijklmnopqrstuvwxyz"
23
+
24
+ def ClassMethods.included(mod)
25
+ mod.instance_eval do
26
+ alias_method :org_table_name, :table_name
27
+ alias_method :table_name, :active_working_table_or_active_table_name
28
+ end
29
+ end
30
+
31
+ # number of historical tables to keep around for posterity, or more likely
32
+ # to ensure running transactions aren't taken down by advance_version
33
+ # recreating a table. default 2
34
+ def historical_version_count
35
+ @historical_version_count || 2
36
+ end
37
+
38
+ # set the number of historical tables to keep around to ensure running
39
+ # transactions aren't interrupted by truncating working tables. 2 is default
40
+ def set_historical_version_count(count)
41
+ @historical_version_count = count
42
+ end
43
+ alias :historical_version_count= :set_historical_version_count
44
+
45
+ # hide the ActiveRecord::Base method, which redefines a table_name method,
46
+ # and instead capture the given name as the base_table_name
47
+ def set_table_name(name)
48
+ @base_table_name = name
49
+ end
50
+ alias :table_name= :set_table_name
51
+
52
+ def base_table_name
53
+ if !@base_table_name
54
+ @base_table_name = org_table_name
55
+ # the original table_name method re-aliases itself !
56
+ class << self
57
+ alias_method :table_name, :active_working_table_or_active_table_name
58
+ end
59
+ end
60
+ @base_table_name
61
+ end
62
+
63
+ # use schema of from table to recreate to table
64
+ def dup_table_schema(from, to)
65
+ connection.execute( "drop table if exists #{to}")
66
+ ct = connection.select_one( "show create table #{from}")["Create Table"]
67
+ ct_no_constraint_names = ct.gsub(/CONSTRAINT `[^`]*`/, "CONSTRAINT ``")
68
+ i = 0
69
+ ct_uniq_constraint_names = ct_no_constraint_names.gsub(/CONSTRAINT ``/) { |s| i+=1 ; "CONSTRAINT `#{to}_#{i}`" }
70
+
71
+ new_ct = ct_uniq_constraint_names.gsub( /CREATE TABLE `#{from}`/, "CREATE TABLE `#{to}`")
72
+ connection.execute(new_ct)
73
+ end
74
+
75
+ def ensure_version_table(name)
76
+ if !connection.table_exists?(name) &&
77
+ base_table_name!=name # don't execute ddl unless necessary
78
+ dup_table_schema(base_table_name, name)
79
+ end
80
+ end
81
+
82
+ # create a switch table of given name, if it doesn't already exist
83
+ def create_switch_table(name)
84
+ connection.execute( "create table if not exists #{name} (`current` varchar(255))" )
85
+ end
86
+
87
+ # create the switch table if it doesn't already exist. return the switch table name
88
+ def ensure_switch_table
89
+ stn = switch_table_name
90
+ if !connection.table_exists?(stn) # don't execute any ddl code if we don't need to
91
+ create_switch_table(stn)
92
+ end
93
+ stn
94
+ end
95
+
96
+ # name of the table with a row holding the active table name
97
+ def switch_table_name
98
+ base_table_name + "_switch"
99
+ end
100
+
101
+ # list of suffixed table names
102
+ def suffixed_table_names
103
+ suffixes = []
104
+ (0...historical_version_count).each{ |i| suffixes << ALPHABET[i...i+1] }
105
+ suffixes.map do |suffix|
106
+ base_table_name + "_" + suffix
107
+ end
108
+ end
109
+
110
+ # ordered vector of table version names, starting with base name
111
+ def table_version_names
112
+ [base_table_name] + suffixed_table_names
113
+ end
114
+
115
+ def default_active_table_name
116
+ # no longer use a different table name for test environments...
117
+ # it makes a mess with named scopes
118
+ base_table_name
119
+ end
120
+
121
+ # name of the active table read direct from db
122
+ def active_table_name
123
+ st = switch_table_name
124
+ begin
125
+ connection.select_value( "select current from #{st}" )
126
+ rescue
127
+ end || default_active_table_name
128
+ end
129
+
130
+ # name of the working table
131
+ def working_table_name
132
+ atn = active_table_name
133
+ tvn = table_version_names
134
+ tvn[ (tvn.index(atn) + 1) % tvn.size ]
135
+ end
136
+
137
+ def ensure_all_tables
138
+ suffixed_table_names.each do |table_name|
139
+ ensure_version_table(table_name)
140
+ end
141
+ ensure_switch_table
142
+ end
143
+
144
+ # make working table active, then recreate new working table from base table schema
145
+ def advance_version
146
+ st = ensure_switch_table
147
+
148
+ # want a transaction at least here [surround is ok too] so
149
+ # there is never an empty switch table
150
+ ActiveRecord::Base.transaction do
151
+ wtn = working_table_name
152
+ connection.execute( "delete from #{st}")
153
+ connection.execute( "insert into #{st} values (\'#{wtn}\')")
154
+ end
155
+
156
+ # ensure the presence of the new active and working tables.
157
+ # happens after the switch table update, since this may commit a surrounding
158
+ # transaction in dbs with retarded non-transactional ddl like, oh i dunno, MyFuckingSQL
159
+ ensure_version_table(active_table_name)
160
+
161
+ # recreate the new working table from the base schema.
162
+ new_wtn = working_table_name
163
+ if new_wtn != base_table_name
164
+ dup_table_schema(base_table_name, new_wtn)
165
+ else
166
+ connection.execute( "truncate table #{new_wtn}" )
167
+ end
168
+ end
169
+
170
+ def thread_local_key_name
171
+ "ActiveRecord::WormTable::" + self.to_s
172
+ end
173
+
174
+ def active_working_table_name
175
+ Thread.current[thread_local_key_name]
176
+ end
177
+
178
+ def active_working_table_name=(name)
179
+ Thread.current[thread_local_key_name] = name
180
+ end
181
+
182
+ # name of the active table, or the working table if inside a new_version block
183
+ def active_working_table_or_active_table_name
184
+ active_working_table_name || active_table_name
185
+ end
186
+
187
+ # make the working table temporarily active [ for this thread only ],
188
+ # execute the block, and if completed without exception then
189
+ # make the working table permanently active
190
+ def new_version(&block)
191
+ begin
192
+ self.active_working_table_name = working_table_name
193
+ ensure_version_table(working_table_name)
194
+ connection.execute("truncate table #{working_table_name}")
195
+ r = block.call
196
+ advance_version
197
+ r
198
+ ensure
199
+ self.active_working_table_name = nil
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'activerecord_worm_table'
3
+ require 'activerecord_worm_table/worm_table'
4
4
  require 'spec'
5
5
  require 'spec/autorun'
6
6
 
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_worm_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 8
8
+ - 1
9
+ version: 0.8.1
5
10
  platform: ruby
6
11
  authors:
7
12
  - mccraig mccraig of the clan mccraig
@@ -9,19 +14,21 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-02-17 00:00:00 +00:00
17
+ date: 2010-03-11 00:00:00 +00:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: rspec
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
23
29
  version: "0"
24
- version:
30
+ type: :development
31
+ version_requirements: *id001
25
32
  description: |-
26
33
  manage WriteOnceReadMany tables backing ActiveRecord models.
27
34
  there will be a switch table and multiple backing tables for each
@@ -45,6 +52,7 @@ files:
45
52
  - Rakefile
46
53
  - VERSION
47
54
  - lib/activerecord_worm_table.rb
55
+ - lib/activerecord_worm_table/worm_table.rb
48
56
  - spec/activerecord_worm_table_spec.rb
49
57
  - spec/spec_helper.rb
50
58
  has_rdoc: true
@@ -60,18 +68,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
68
  requirements:
61
69
  - - ">="
62
70
  - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
63
73
  version: "0"
64
- version:
65
74
  required_rubygems_version: !ruby/object:Gem::Requirement
66
75
  requirements:
67
76
  - - ">="
68
77
  - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
69
80
  version: "0"
70
- version:
71
81
  requirements: []
72
82
 
73
83
  rubyforge_project:
74
- rubygems_version: 1.3.5
84
+ rubygems_version: 1.3.6
75
85
  signing_key:
76
86
  specification_version: 3
77
87
  summary: WORM tables for ActiveRecord models