mcfly 0.0.1
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/.gitignore +21 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +113 -0
- data/Rakefile +27 -0
- data/lib/mcfly/controller.rb +21 -0
- data/lib/mcfly/delete_trig.sql +18 -0
- data/lib/mcfly/has_mcfly.rb +63 -0
- data/lib/mcfly/insert_trig.sql +27 -0
- data/lib/mcfly/migration.rb +27 -0
- data/lib/mcfly/update_append_only_trig.sql +25 -0
- data/lib/mcfly/update_trig.sql +62 -0
- data/lib/mcfly/version.rb +3 -0
- data/lib/mcfly.rb +36 -0
- data/lib/tasks/mcfly_tasks.rake +4 -0
- data/mcfly.gemspec +27 -0
- data/spec/dummy/.rspec +1 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/market_price.rb +26 -0
- data/spec/dummy/app/models/security_instrument.rb +15 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +17 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/001_create_security_instruments.rb +8 -0
- data/spec/dummy/db/migrate/002_create_market_prices.rb +14 -0
- data/spec/dummy/db/schema.rb +37 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/log/development.log +3 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/model_spec.rb +206 -0
- data/spec/spec_helper.rb +51 -0
- metadata +184 -0
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
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
|
18
|
+
*~
|
19
|
+
*.komodoproject
|
20
|
+
.rvmrc
|
21
|
+
spec/dummy/log
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Arman Bostani
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Mcfly
|
2
|
+
|
3
|
+
Mcfly is a database table versioning system. It's useful for tracking
|
4
|
+
and auditing changes to database tables. It's also very easy to
|
5
|
+
access the current state of Mcfly tables at any point in time.
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
* All row versions are stored in the same table.
|
12
|
+
|
13
|
+
* Different row versions are accessed through scoping.
|
14
|
+
|
15
|
+
* Applications can use Mcfly to time-warp all tables to previous
|
16
|
+
points in time.
|
17
|
+
|
18
|
+
* Table queries for points in time are symmetric. i.e. queries to
|
19
|
+
access data in the present look just like queries available in any
|
20
|
+
particular point in time.
|
21
|
+
|
22
|
+
* Implemented as database triggers. So, the versioning system is
|
23
|
+
language/platform agnostic.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
$ gem install mcfly
|
28
|
+
|
29
|
+
Or add it to your `Gemfile`, etc.
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
To create Mcfly enabled tables, they need to be created using
|
34
|
+
`McFly::McFlyMigration` or `McFly::McFlyAppendOnlyMigration` instead
|
35
|
+
of the usual `ActiveRecord::Migration`.
|
36
|
+
|
37
|
+
class CreateSecurityInstruments < McFlyAppendOnlyMigration
|
38
|
+
def change
|
39
|
+
create_table :security_instruments do |t|
|
40
|
+
t.string :name, null: false
|
41
|
+
t.string :settlement_class, limit: 1, null: false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class CreateMarketPrices < McFlyMigration
|
47
|
+
def change
|
48
|
+
create_table :market_prices do |t|
|
49
|
+
t.references :security_instrument, null: false
|
50
|
+
t.decimal :coupon, null: false
|
51
|
+
t.integer :settlement_mm, null: false
|
52
|
+
t.integer :settlement_yy, null: false
|
53
|
+
# NULL indicates unknown price
|
54
|
+
t.decimal :price
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
These migration add the necessary versioning triggers for INSERT,
|
60
|
+
UPDATE and DELETE operations. The append-only migration disallows
|
61
|
+
updates. As such, append-only Mcfly tables rows to be INSERTed or
|
62
|
+
DELETEed, but not modified.
|
63
|
+
|
64
|
+
When you declare `has_mcfly` in your model, Mcfly adds some basic
|
65
|
+
functionality to the class.
|
66
|
+
|
67
|
+
class SecurityInstrument < ActiveRecord::Base
|
68
|
+
has_mcfly append_only: true
|
69
|
+
|
70
|
+
attr_accessible :name, :settlement_class
|
71
|
+
validates_presence_of :name, :settlement_class
|
72
|
+
mcfly_validates_uniqueness_of :name
|
73
|
+
|
74
|
+
mcfly_lookup :lookup, sig: 2 do
|
75
|
+
|pt, name|
|
76
|
+
find_by_name(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
mcfly_lookup :lookup_all, sig: 1 do
|
80
|
+
|pt| all
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
The `has_mcfly` declaration provides the `mcfly_lookup` generator
|
85
|
+
which is scopes queries to the proper timeline. Also,
|
86
|
+
`mcfly_validates_uniqueness_of` is Mcfly's scoped version of
|
87
|
+
ActiveRecord's `validates_uniqueness_of`.
|
88
|
+
|
89
|
+
... TODO ... show examples of adding rows and accessing versions of
|
90
|
+
data ...
|
91
|
+
|
92
|
+
## Implementation
|
93
|
+
|
94
|
+
TODO
|
95
|
+
|
96
|
+
## Limitations
|
97
|
+
|
98
|
+
Currently, Mcfly only works with PostgreSQL databases.
|
99
|
+
|
100
|
+
## History
|
101
|
+
|
102
|
+
The database table versioning mechanism used in Mcfly was originally
|
103
|
+
developed at [TWINSUN][]. It has since been modified and enhanced at
|
104
|
+
[PENNYMAC][].
|
105
|
+
|
106
|
+
## License
|
107
|
+
|
108
|
+
Delorean has been released under the MIT license. Please check the
|
109
|
+
[LICENSE][] file for more details.
|
110
|
+
|
111
|
+
[license]: https://github.com/rubygems/rubygems.org/blob/master/MIT-LICENSE
|
112
|
+
[pennymac]: http://www.pennymacusa.com
|
113
|
+
[twinsun]: http://www.twinsun.com
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Mcfly'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mcfly
|
2
|
+
module Controller
|
3
|
+
def self.included(base)
|
4
|
+
base.before_filter :set_mcfly_whodunnit
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the user who is responsible for any changes that occur.
|
8
|
+
# By default this calls `current_user` and returns the result.
|
9
|
+
#
|
10
|
+
# Override this method in your controller to call a different
|
11
|
+
# method, e.g. `current_person`, or anything you like.
|
12
|
+
def user_for_mcfly
|
13
|
+
current_user rescue nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# Tells Mcfly who is responsible for any changes that occur.
|
17
|
+
def set_mcfly_whodunnit
|
18
|
+
::Mcfly.whodunnit = user_for_mcfly
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION "%{table}_delete" ()
|
2
|
+
RETURNS TRIGGER
|
3
|
+
AS $$
|
4
|
+
|
5
|
+
BEGIN
|
6
|
+
IF OLD.obsoleted_dt <> 'infinity' THEN
|
7
|
+
RAISE EXCEPTION 'can not delete old row version';
|
8
|
+
END IF;
|
9
|
+
|
10
|
+
UPDATE "%{table}" SET "obsoleted_dt" = 'now()' WHERE id = OLD.id;
|
11
|
+
|
12
|
+
RETURN NULL; -- the row is not actually deleted
|
13
|
+
END;
|
14
|
+
$$ LANGUAGE plpgsql;
|
15
|
+
|
16
|
+
DROP TRIGGER IF EXISTS %{table}_delete ON %{table};
|
17
|
+
CREATE TRIGGER "%{table}_delete" BEFORE DELETE ON "%{table}" FOR EACH ROW
|
18
|
+
EXECUTE PROCEDURE "%{table}_delete"();
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'delorean_lang'
|
2
|
+
|
3
|
+
module McFly
|
4
|
+
module Model
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def has_mcfly(options = {})
|
12
|
+
# FIXME: this methods gets a append_only option sometimes. It
|
13
|
+
# needs to add model level validations which prevent update
|
14
|
+
# when this option is present. Note that we need to allow
|
15
|
+
# delete. Deletion of McFly objects obsoletes them by setting
|
16
|
+
# obsoleted_dt.
|
17
|
+
|
18
|
+
send :include, InstanceMethods
|
19
|
+
after_initialize :record_init
|
20
|
+
# FIXME: :created_dt should also be readonly. However, we set
|
21
|
+
# it for debugging purposes. Should consider making this
|
22
|
+
# readonly once we're in production.
|
23
|
+
attr_readonly :group_id, :obsoleted_dt, :user_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def mcfly_lookup(name, options = {}, &block)
|
27
|
+
delorean_fn(name, options) do |t, *args|
|
28
|
+
raise "time cannot be nil" if t.nil?
|
29
|
+
ts = (t == Float::INFINITY) ? 'infinity' : t
|
30
|
+
self.where("obsoleted_dt >= ? AND created_dt < ?", ts, ts).scoping do
|
31
|
+
block.call(t, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def mcfly_validates_uniqueness_of(*attr_names)
|
37
|
+
# add :obsoleted_dt to the uniqueness scope
|
38
|
+
|
39
|
+
attr_names << {} unless attr_names.last.is_a?(Hash)
|
40
|
+
|
41
|
+
attr_names.last[:scope] ||= []
|
42
|
+
attr_names.last[:scope] << :obsoleted_dt
|
43
|
+
|
44
|
+
# Set uniqueness error message if not set. FIXME: need to
|
45
|
+
# figure out how to change the base message. It still
|
46
|
+
# prepends the pluralized main attr.
|
47
|
+
attr_names.last[:message] ||= "- record must be unique"
|
48
|
+
|
49
|
+
validates_uniqueness_of(*attr_names)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module InstanceMethods
|
54
|
+
def record_init
|
55
|
+
# Set obsoleted_dt to non NIL to ensure constraints are properly
|
56
|
+
# constructed
|
57
|
+
self.obsoleted_dt = 'infinity' unless self.obsoleted_dt
|
58
|
+
self.user_id ||= Mcfly.whodunnit.try(:id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION "%{table}_insert" ()
|
2
|
+
RETURNS TRIGGER
|
3
|
+
AS $$
|
4
|
+
BEGIN
|
5
|
+
-- "obsoleted_dt" is set when a history row is created by
|
6
|
+
-- UPDATE. Leave it alone.
|
7
|
+
IF NEW.obsoleted_dt <> 'infinity' THEN
|
8
|
+
RETURN NEW;
|
9
|
+
END IF;
|
10
|
+
|
11
|
+
NEW.obsoleted_dt = 'infinity';
|
12
|
+
NEW.group_id = NEW.id;
|
13
|
+
|
14
|
+
-- FIXME: Handle cases where created_dt is sent in on creation. This
|
15
|
+
-- is only useful for debugging. Consider removing the surronding
|
16
|
+
-- IF for production version.
|
17
|
+
IF NEW.created_dt IS NULL THEN
|
18
|
+
NEW.created_dt = 'now()';
|
19
|
+
END IF;
|
20
|
+
|
21
|
+
RETURN NEW;
|
22
|
+
END;
|
23
|
+
$$ LANGUAGE plpgsql;
|
24
|
+
|
25
|
+
DROP TRIGGER IF EXISTS %{table}_insert ON %{table};
|
26
|
+
CREATE TRIGGER "%{table}_insert" BEFORE INSERT ON "%{table}" FOR EACH ROW
|
27
|
+
EXECUTE PROCEDURE "%{table}_insert"();
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class McFlyMigration < ActiveRecord::Migration
|
2
|
+
INSERT_TRIG, UPDATE_TRIG, UPDATE_APPEND_ONLY_TRIG, DELETE_TRIG =
|
3
|
+
%w{insert_trig update_trig update_append_only_trig delete_trig}.map { |f|
|
4
|
+
File.read(File.dirname(__FILE__) + "/#{f}.sql")
|
5
|
+
}
|
6
|
+
|
7
|
+
TRIGS = [INSERT_TRIG, UPDATE_TRIG, DELETE_TRIG]
|
8
|
+
|
9
|
+
def create_table(table_name, options = {}, &block)
|
10
|
+
super { |t|
|
11
|
+
t.integer :group_id, null: false
|
12
|
+
# can't use created_at/updated_at as those are automatically
|
13
|
+
# filled by ActiveRecord.
|
14
|
+
t.timestamp :created_dt, null: false
|
15
|
+
t.timestamp :obsoleted_dt, null: false
|
16
|
+
t.references :user, null: false
|
17
|
+
block.call(t)
|
18
|
+
}
|
19
|
+
|
20
|
+
self.class::TRIGS.each {|sql| execute sql % {table: table_name}}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class McFlyAppendOnlyMigration < McFlyMigration
|
25
|
+
# append-only update trigger disallows updates
|
26
|
+
TRIGS = [INSERT_TRIG, UPDATE_APPEND_ONLY_TRIG, DELETE_TRIG]
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION "%{table}_update" ()
|
2
|
+
RETURNS TRIGGER
|
3
|
+
AS $$
|
4
|
+
DECLARE
|
5
|
+
|
6
|
+
BEGIN
|
7
|
+
IF OLD.obsoleted_dt <> 'infinity' THEN
|
8
|
+
RAISE EXCEPTION 'can not update obsoleted append-only row';
|
9
|
+
END IF;
|
10
|
+
|
11
|
+
-- If obsoleted_dt is being set, assume that the row is being
|
12
|
+
-- obsoleted. We return the OLD row so that other field updates are
|
13
|
+
-- ignored. This is used by DELETE.
|
14
|
+
IF NEW.obsoleted_dt <> 'infinity' THEN
|
15
|
+
OLD.obsoleted_dt = NEW.obsoleted_dt;
|
16
|
+
return OLD;
|
17
|
+
END IF;
|
18
|
+
|
19
|
+
RAISE EXCEPTION 'can not update append-only row';
|
20
|
+
END;
|
21
|
+
$$ LANGUAGE plpgsql;
|
22
|
+
|
23
|
+
DROP TRIGGER IF EXISTS %{table}_update ON %{table};
|
24
|
+
CREATE TRIGGER "%{table}_update" BEFORE UPDATE ON "%{table}" FOR EACH ROW
|
25
|
+
EXECUTE PROCEDURE "%{table}_update"();
|
@@ -0,0 +1,62 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION "%{table}_update" ()
|
2
|
+
RETURNS TRIGGER
|
3
|
+
AS $$
|
4
|
+
DECLARE
|
5
|
+
rec "%{table}";
|
6
|
+
new_id INT4;
|
7
|
+
now timestamp;
|
8
|
+
|
9
|
+
BEGIN
|
10
|
+
IF OLD.obsoleted_dt <> 'infinity' THEN
|
11
|
+
RAISE EXCEPTION 'can not modify old row version';
|
12
|
+
END IF;
|
13
|
+
|
14
|
+
-- If obsoleted_dt is being set, assume that the row is being
|
15
|
+
-- obsoleted. We return the OLD row so that other field updates are
|
16
|
+
-- ignored. This is used by DELETE.
|
17
|
+
IF NEW.obsoleted_dt <> 'infinity' THEN
|
18
|
+
OLD.obsoleted_dt = NEW.obsoleted_dt;
|
19
|
+
return OLD;
|
20
|
+
END IF;
|
21
|
+
|
22
|
+
-- copy old version of the row into rec
|
23
|
+
SELECT INTO rec * FROM "%{table}" WHERE "id" = NEW.id;
|
24
|
+
|
25
|
+
-- new_id is a new primary key that we'll use for the obsoleted row.
|
26
|
+
SELECT nextval('"%{table}_id_seq"') INTO new_id;
|
27
|
+
|
28
|
+
-- not sure if PGSQL will return the same value for now() in the
|
29
|
+
-- same transaction. So, use the same variable to be sure.
|
30
|
+
now = 'now()';
|
31
|
+
|
32
|
+
rec.id = new_id;
|
33
|
+
rec.group_id = NEW.id;
|
34
|
+
|
35
|
+
-- FIXME: The following IF/ELSE handles cases where created_dt is
|
36
|
+
-- sent in on update. This is only useful for debugging. Consider
|
37
|
+
-- removing the surronding IF (and ELSE part) for production
|
38
|
+
-- version.
|
39
|
+
IF NEW.created_dt = OLD.created_dt THEN
|
40
|
+
-- Set the modified row's created_dt. The obsoleted_dt field was
|
41
|
+
-- already infinity, so we don't need to set it.
|
42
|
+
NEW.created_dt = now;
|
43
|
+
rec.obsoleted_dt = now;
|
44
|
+
ELSE
|
45
|
+
IF NEW.created_dt <= OLD.created_dt THEN
|
46
|
+
RAISE EXCEPTION 'new created_dt must be greater than old';
|
47
|
+
END IF;
|
48
|
+
|
49
|
+
rec.obsoleted_dt = NEW.created_dt;
|
50
|
+
END IF;
|
51
|
+
|
52
|
+
-- insert rec, note that the insert trigger will get called. The
|
53
|
+
-- obsoleted_dt is set so INSERT should not do anything with this row.
|
54
|
+
INSERT INTO "%{table}" VALUES (rec.*);
|
55
|
+
|
56
|
+
RETURN NEW;
|
57
|
+
END;
|
58
|
+
$$ LANGUAGE plpgsql;
|
59
|
+
|
60
|
+
DROP TRIGGER IF EXISTS %{table}_update ON %{table};
|
61
|
+
CREATE TRIGGER "%{table}_update" BEFORE UPDATE ON "%{table}" FOR EACH ROW
|
62
|
+
EXECUTE PROCEDURE "%{table}_update"();
|
data/lib/mcfly.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mcfly/migration'
|
2
|
+
require 'mcfly/has_mcfly'
|
3
|
+
require 'mcfly/controller'
|
4
|
+
require 'active_support'
|
5
|
+
|
6
|
+
module Mcfly
|
7
|
+
# ATTRIBUTION: some of the code in this project has been shamelessly
|
8
|
+
# lifted form paper_trail.
|
9
|
+
|
10
|
+
# Sets who is responsible for any changes that occur. You would
|
11
|
+
# normally use this in a migration or on the console, when working
|
12
|
+
# with models directly.
|
13
|
+
def self.whodunnit=(value)
|
14
|
+
mcfly_store[:whodunnit] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.whodunnit
|
18
|
+
mcfly_store[:whodunnit]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Thread-safe hash to hold Mcfly's data.
|
24
|
+
def self.mcfly_store
|
25
|
+
Thread.current[:mcfly] ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ActiveSupport.on_load(:active_record) do
|
30
|
+
include Delorean::Model
|
31
|
+
include McFly::Model
|
32
|
+
end
|
33
|
+
|
34
|
+
ActiveSupport.on_load(:action_controller) do
|
35
|
+
include Mcfly::Controller
|
36
|
+
end
|
data/mcfly.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require "mcfly/version"
|
4
|
+
|
5
|
+
# Describe your gem and declare its dependencies:
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "mcfly"
|
8
|
+
s.version = Mcfly::VERSION
|
9
|
+
s.authors = ["Arman Bostani"]
|
10
|
+
s.email = ["arman.bostani@pnmac.com"]
|
11
|
+
s.homepage = "https://github.com/arman000/mcfly"
|
12
|
+
s.summary = %q{A database table versioning system.}
|
13
|
+
s.description = s.summary
|
14
|
+
s.files = `git ls-files`.split($\)
|
15
|
+
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency "rails", "~> 3.2.11"
|
19
|
+
s.add_dependency "pg"
|
20
|
+
|
21
|
+
# FIXME: Delorean is added here for historical reasons. It should
|
22
|
+
# be removed as a dependency.
|
23
|
+
s.add_dependency "delorean_lang"
|
24
|
+
|
25
|
+
s.add_development_dependency "rspec"
|
26
|
+
s.add_development_dependency "rspec-rails"
|
27
|
+
end
|
data/spec/dummy/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|