logidze 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f91810e96c04d3ddc2d629c5aa0179c69a232d52
4
- data.tar.gz: 633e81904255a8d8186dc3839c59bb8c84623276
3
+ metadata.gz: 937e0b13769d464f7bb14dc1440951ea6a1462db
4
+ data.tar.gz: e7d117211dc9e24854b0171158996066aa56ade1
5
5
  SHA512:
6
- metadata.gz: 039e4e7543e6a9e5415eccd73921fff6a850a25e11b68b437d25356ede09f7c89e713803fdad7c77d8687d22a5ce2fae2809a6765d435f1ffc415c85e6ca33dc
7
- data.tar.gz: 0db11276109aa07f6b2350e2acd4034b6cfe4f76325ba97f16ea14306c949fca0362520246454ad65e1becad13411d58ea5d7c7e6c09dd233dceca680a9a7453
6
+ metadata.gz: fcdfe2828fa0fa360177a7839de761bc91b7e9bb81e9ce0d69482e28b3d2c1194a91e71dca9e13975d7eafc31839db3990f7b082fa37f132c43b51bad3245010
7
+ data.tar.gz: 3fd4562dd287fdcf8c1321e5ff8e225931cbc9b76913f1c90f9a4566f2610446f86febddfed60407988746a4ad765d397d469bef75eb06591938b2978198866c
@@ -1,3 +1,11 @@
1
+ # 0.3.0
2
+ - Add `--update` option to install migration generator
3
+ - Add `--only-trigger` option to model migration generator
4
+ - Add [Responsibility](https://github.com/palkan/logidze/issues/4) feature
5
+
6
+ # 0.2.3
7
+ - Support Ruby >= 2.1
8
+
1
9
  # 0.2.2
2
10
  - Add `--backfill` option to model migration
3
11
  - Handle legacy data (that doesn't have log data)
data/README.md CHANGED
@@ -60,6 +60,16 @@ To backfill table data (i.e. create initial snapshots) add `backfill` option:
60
60
  rails generate logidze:model Post --backfill
61
61
  ```
62
62
 
63
+ ## Upgrade from previous versions
64
+
65
+ We try to make upgrade process as simple as possible. For now, the only required action is to create and run a migration:
66
+
67
+ ```ruby
68
+ rails generate logidze:install --update
69
+ ```
70
+
71
+ This updates core `logdize_logger` DB function. No need to update tables or triggers.
72
+
63
73
  ## Usage
64
74
 
65
75
  Your model now has `log_data` column which stores changes log.
@@ -122,6 +132,49 @@ post.switch_to!(2)
122
132
 
123
133
  If you update record after `#undo!` or `#switch_to!` you lose all "future" versions and `#redo!` is no longer possible.
124
134
 
135
+ ## Track responsibility (aka _whodunnit_)
136
+
137
+ You can store additional information in the version object, which is called _Responsible ID_. There is more likely that you would like to store the `current_user.id` that way.
138
+
139
+ To provide `responsible_id` you should wrap your code in a block:
140
+
141
+ ```ruby
142
+ Logidze.with_responsible(user.id) do
143
+ post.save!
144
+ end
145
+ ```
146
+
147
+ And then to retrieve `responsible_id`:
148
+
149
+ ```ruby
150
+ post.log_data.responsible_id
151
+ ```
152
+
153
+ Logidze does not require `responsible_id` to be `SomeModel` ID. It can be anything. Thus Logidze does not provide methods for retrieving the corresponding object. However, you can easy write it yourself:
154
+
155
+ ```ruby
156
+ class Post < ActiveRecord::Base
157
+ has_logidze
158
+
159
+ def whodunnit
160
+ id = log_data.responsible_id
161
+ User.find(id) if id.present?
162
+ end
163
+ end
164
+ ```
165
+
166
+ And in your controller:
167
+
168
+ ```ruby
169
+ class ApplicationController < ActionController::Base
170
+ around_action :set_logidze_responsible, only: [:create, :update]
171
+
172
+ def set_logidze_responsible(&block)
173
+ Logidze.with_responsible(current_user&.id, &block)
174
+ end
175
+ end
176
+ ```
177
+
125
178
  ## Disable logging temporary
126
179
 
127
180
  If you want to make update without logging (e.g. mass update), you can turn it off the following way:
@@ -150,7 +203,8 @@ The `log_data` column has the following format:
150
203
  "attr": "new value", // updated fields with new values
151
204
  "attr2": "new value"
152
205
  }
153
- }
206
+ },
207
+ "r": 42 // Resposibility ID (if provided)
154
208
  ]
155
209
  }
156
210
  ```
@@ -9,14 +9,36 @@ module Logidze
9
9
 
10
10
  source_root File.expand_path('../templates', __FILE__)
11
11
 
12
+ class_option :update, type: :boolean, optional: true,
13
+ desc: "Define whether this is an update migration"
14
+
12
15
  def generate_migration
13
- migration_template "migration.rb.erb", "db/migrate/logidze_install.rb"
16
+ migration_template "migration.rb.erb", "db/migrate/#{migration_name}.rb"
14
17
  end
15
18
 
16
19
  def generate_hstore_migration
20
+ return if update?
17
21
  migration_template "hstore.rb.erb", "db/migrate/enable_hstore.rb"
18
22
  end
19
23
 
24
+ no_tasks do
25
+ def migration_name
26
+ if update?
27
+ "logidze_update_#{Logidze::VERSION.delete('.')}"
28
+ else
29
+ "logidze_install"
30
+ end
31
+ end
32
+
33
+ def migration_class_name
34
+ migration_name.classify
35
+ end
36
+
37
+ def update?
38
+ options[:update]
39
+ end
40
+ end
41
+
20
42
  def self.next_migration_number(dir)
21
43
  ::ActiveRecord::Generators::Base.next_migration_number(dir)
22
44
  end
@@ -4,25 +4,39 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
4
4
  DO $$
5
5
  BEGIN
6
6
  EXECUTE 'ALTER DATABASE ' || current_database() || ' SET logidze.disabled TO off';
7
+ EXECUTE 'ALTER DATABASE ' || current_database() || ' SET logidze.responsible TO off';
7
8
  END;
8
9
  $$
9
10
  LANGUAGE plpgsql;
10
11
  SQL
11
12
 
12
13
  execute <<-SQL
14
+ CREATE OR REPLACE FUNCTION logidze_version(v bigint, data jsonb) RETURNS jsonb AS $body$
15
+ DECLARE
16
+ buf jsonb;
17
+ BEGIN
18
+ buf := jsonb_build_object(
19
+ 'ts',
20
+ (extract(epoch from now()) * 1000)::bigint,
21
+ 'v',
22
+ v,
23
+ 'c',
24
+ logidze_exclude_keys(data, 'log_data')
25
+ );
26
+ IF current_setting('logidze.responsible') <> 'off' THEN
27
+ buf := jsonb_set(buf, ARRAY['r'], to_jsonb(current_setting('logidze.responsible')));
28
+ END IF;
29
+ RETURN buf;
30
+ END;
31
+ $body$
32
+ LANGUAGE plpgsql;
33
+
13
34
  CREATE OR REPLACE FUNCTION logidze_snapshot(item jsonb) RETURNS jsonb AS $body$
14
35
  BEGIN
15
36
  return json_build_object(
16
37
  'v', 1,
17
38
  'h', jsonb_build_array(
18
- jsonb_build_object(
19
- 'ts',
20
- (extract(epoch from now()) * 1000)::bigint,
21
- 'v',
22
- 1,
23
- 'c',
24
- logidze_exclude_keys(item,'log_data')
25
- )
39
+ logidze_version(1, item)
26
40
  )
27
41
  );
28
42
  END;
@@ -57,6 +71,10 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
57
71
  (log_data#>'{h,0,c}') || (log_data#>'{h,1,c}')
58
72
  );
59
73
 
74
+ IF (log_data#>'{h,1}' ? 'r') THEN
75
+ merged := jsonb_set(merged, ARRAY['r'], log_data#>'{h,1,r}');
76
+ END IF;
77
+
60
78
  return jsonb_set(
61
79
  log_data,
62
80
  '{h}',
@@ -74,7 +92,6 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
74
92
  DECLARE
75
93
  changes jsonb;
76
94
  new_v integer;
77
- ts bigint;
78
95
  size integer;
79
96
  history_limit integer;
80
97
  current_version integer;
@@ -101,8 +118,6 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
101
118
  RETURN NEW;
102
119
  END IF;
103
120
 
104
- ts := (extract(epoch from now()) * 1000)::bigint;
105
-
106
121
  IF current_version < (NEW.log_data#>>'{h,-1,v}')::int THEN
107
122
  iterator := 0;
108
123
  FOR item in SELECT * FROM jsonb_array_elements(NEW.log_data->'h')
@@ -118,11 +133,8 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
118
133
  END LOOP;
119
134
  END IF;
120
135
 
121
- changes := logidze_exclude_keys(
122
- hstore_to_jsonb_loose(
123
- hstore(NEW.*) - hstore(OLD.*)
124
- ),
125
- 'log_data'
136
+ changes := hstore_to_jsonb_loose(
137
+ hstore(NEW.*) - hstore(OLD.*)
126
138
  );
127
139
 
128
140
  new_v := (NEW.log_data#>>'{h,-1,v}')::int + 1;
@@ -132,14 +144,7 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
132
144
  NEW.log_data := jsonb_set(
133
145
  NEW.log_data,
134
146
  ARRAY['h', size::text],
135
- jsonb_build_object(
136
- 'ts',
137
- ts,
138
- 'v',
139
- new_v,
140
- 'c',
141
- changes
142
- ),
147
+ logidze_version(new_v, changes),
143
148
  true
144
149
  );
145
150
 
@@ -162,10 +167,13 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
162
167
  end
163
168
 
164
169
  def down
170
+ <% unless update? %>
165
171
  execute <<-SQL
172
+ DROP FUNCTION logidze_version(bigint, jsonb) CASCADE;
166
173
  DROP FUNCTION logidze_compact_history(jsonb) CASCADE;
167
174
  DROP FUNCTION logidze_snapshot(jsonb) CASCADE;
168
175
  DROP FUNCTION logidze_logger() CASCADE;
169
176
  SQL
177
+ <% end %>
170
178
  end
171
179
  end
@@ -12,6 +12,9 @@ module Logidze
12
12
  class_option :backfill, type: :boolean, optional: true,
13
13
  desc: "Add query to backfill existing records history"
14
14
 
15
+ class_option :only_trigger, type: :boolean, optional: true,
16
+ desc: "Create trigger-only migration"
17
+
15
18
  def generate_migration
16
19
  migration_template "migration.rb.erb", "db/migrate/#{migration_file_name}"
17
20
  end
@@ -35,9 +38,13 @@ module Logidze
35
38
  options[:limit]
36
39
  end
37
40
 
38
- def backfill
41
+ def backfill?
39
42
  options[:backfill]
40
43
  end
44
+
45
+ def only_trigger?
46
+ options[:only_trigger]
47
+ end
41
48
  end
42
49
 
43
50
  private
@@ -1,6 +1,8 @@
1
1
  class <%= @migration_class_name %> < ActiveRecord::Migration
2
2
  def up
3
- add_column :<%= table_name %>, :log_data, :jsonb, default: {}, null: false
3
+ <% unless only_trigger? %>
4
+ add_column :<%= table_name %>, :log_data, :jsonb
5
+ <% end %>
4
6
 
5
7
  execute <<-SQL
6
8
  CREATE TRIGGER logidze_on_<%= table_name %>
@@ -9,7 +11,7 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
9
11
  EXECUTE PROCEDURE logidze_logger(<%= limit || '' %>);
10
12
  SQL
11
13
 
12
- <% if backfill %>
14
+ <% if backfill? %>
13
15
  execute <<-SQL
14
16
  UPDATE <%= table_name %> as t
15
17
  SET log_data = logidze_snapshot(to_jsonb(t));
@@ -20,6 +22,8 @@ class <%= @migration_class_name %> < ActiveRecord::Migration
20
22
  def down
21
23
  execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
22
24
 
25
+ <% unless only_trigger? %>
23
26
  remove_column :<%= table_name %>, :log_data
27
+ <% end %>
24
28
  end
25
29
  end
@@ -7,6 +7,9 @@ module Logidze
7
7
  require 'logidze/history'
8
8
  require 'logidze/model'
9
9
  require 'logidze/has_logidze'
10
+ require 'logidze/responsible'
11
+
12
+ extend Logidze::Responsible
10
13
 
11
14
  require 'logidze/engine' if defined?(Rails)
12
15
 
@@ -12,6 +12,7 @@ module Logidze
12
12
  attr_reader :data
13
13
 
14
14
  delegate :size, to: :versions
15
+ delegate :responsible_id, to: :current_version
15
16
 
16
17
  ### Rails 4 ###
17
18
  def self.dump(object)
@@ -105,6 +106,10 @@ module Logidze
105
106
  versions.reverse.find { |v| v.time <= time }
106
107
  end
107
108
 
109
+ def dup
110
+ self.class.new(data.deep_dup)
111
+ end
112
+
108
113
  def ==(other)
109
114
  return super unless other.is_a?(self.class)
110
115
  data == other.data
@@ -7,6 +7,8 @@ module Logidze
7
7
  TS = 'ts'
8
8
  # Changes key
9
9
  CHANGES = 'c'
10
+ # Responsible ID
11
+ RESPONSIBLE = 'r'
10
12
 
11
13
  attr_reader :data
12
14
 
@@ -25,6 +27,10 @@ module Logidze
25
27
  def time
26
28
  data.fetch(TS)
27
29
  end
30
+
31
+ def responsible_id
32
+ data[RESPONSIBLE]
33
+ end
28
34
  end
29
35
  end
30
36
  end
@@ -46,8 +46,10 @@ module Logidze
46
46
  return nil unless log_data.exists_ts?(ts)
47
47
  return self if log_data.current_ts?(ts)
48
48
 
49
+ version = log_data.find_by_time(ts).version
50
+
49
51
  object_at = dup
50
- object_at.apply_diff(log_data.changes_to(time: ts))
52
+ object_at.apply_diff(version, log_data.changes_to(version: version))
51
53
  end
52
54
 
53
55
  # Revert record to the version at specified time (without saving to DB)
@@ -56,7 +58,9 @@ module Logidze
56
58
  return self if log_data.current_ts?(ts)
57
59
  return false unless log_data.exists_ts?(ts)
58
60
 
59
- apply_diff(log_data.changes_to(time: ts))
61
+ version = log_data.find_by_time(ts).version
62
+
63
+ apply_diff(version, log_data.changes_to(version: version))
60
64
  end
61
65
 
62
66
  # Return a dirty copy of specified version of record
@@ -65,7 +69,7 @@ module Logidze
65
69
  return nil unless log_data.find_by_version(version)
66
70
 
67
71
  object_at = dup
68
- object_at.apply_diff(log_data.changes_to(version: version))
72
+ object_at.apply_diff(version, log_data.changes_to(version: version))
69
73
  end
70
74
 
71
75
  # Revert record to the specified version (without saving to DB)
@@ -73,7 +77,7 @@ module Logidze
73
77
  return self if log_data.version == version
74
78
  return false unless log_data.find_by_version(version)
75
79
 
76
- apply_diff(log_data.changes_to(version: version))
80
+ apply_diff(version, log_data.changes_to(version: version))
77
81
  end
78
82
 
79
83
  # Return diff object representing changes since specified time.
@@ -107,14 +111,14 @@ module Logidze
107
111
  # Return false if version is unknown.
108
112
  def switch_to!(version)
109
113
  return false unless at_version!(version)
110
- log_data.version = version
111
114
  self.class.without_logging { save! }
112
115
  end
113
116
 
114
117
  protected
115
118
 
116
- def apply_diff(diff)
119
+ def apply_diff(version, diff)
117
120
  diff.each { |k, v| send("#{k}=", v) }
121
+ log_data.version = version
118
122
  self
119
123
  end
120
124
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module Logidze # :nodoc:
3
+ # Provide methods to work with "responsibility" feature
4
+ module Responsible
5
+ def with_responsible(responsible_id)
6
+ return yield if responsible_id.nil?
7
+ ActiveRecord::Base.transaction do
8
+ ActiveRecord::Base.connection.execute(
9
+ "SET LOCAL logidze.responsible = #{ActiveRecord::Base.connection.quote(responsible_id)};"
10
+ )
11
+ res = yield
12
+ ActiveRecord::Base.connection.execute "SET LOCAL logidze.responsible = DEFAULT;"
13
+ res
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Logidze
3
- VERSION = "0.2.3"
3
+ VERSION = "0.3.0"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logidze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-09 00:00:00.000000000 Z
11
+ date: 2016-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -186,6 +186,7 @@ files:
186
186
  - lib/logidze/history/type.rb
187
187
  - lib/logidze/history/version.rb
188
188
  - lib/logidze/model.rb
189
+ - lib/logidze/responsible.rb
189
190
  - lib/logidze/version.rb
190
191
  - logidze.gemspec
191
192
  homepage: http://github.com/palkan/logidze