historyable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- checksums.yaml.gz.sig +0 -0
- data/lib/generators/historyable/USAGE +2 -0
- data/lib/generators/historyable/install_generator.rb +24 -0
- data/lib/generators/historyable/templates/migration.rb +16 -0
- data/lib/historyable/change.rb +4 -0
- data/lib/historyable/version.rb +3 -0
- data/lib/historyable.rb +102 -0
- data.tar.gz.sig +0 -0
- metadata +95 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NWE3OTczNmRmZmQxY2RhMzc0OTk1MTJlY2JjMGUwY2E1YjlkMTJkNQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTZhYWFhOWQwZGIzNGNhNWMwNTlhM2U4OTJlNWY3YjM5OGUwMmU4NA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjUyZTBiZDRhMmRmYmVhZmYyOTU4ZjBjMGU5MDc1ODZhOTUwZjE3ZWIxY2Vm
|
10
|
+
ZDM5MGE5ZDhmOGIwZWY3OTY1MDUzZDM3YWQ4MzBlY2NiYzE3YWQ3YzkwZTMw
|
11
|
+
ZjJlMzNjOGNhMTQwNTM3OWU1YThhMmM2MmQ0YTYwNTYyNzJjY2M=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MWI1YzFmZmZiODZkNzg4ZDU2ZTRlM2RlN2NjYjk0YzY0NmI0NDc1M2Y5ZWEy
|
14
|
+
ZDU4MTRhZDZjMmNmMzEzZDJmNmUyYTU5NjllOWI4MDA3YzQ5Y2NkYmQ3NTUw
|
15
|
+
NThhYTczM2FiOWFmNjY3YWE3MTc2MzYxNmRjMmVlMTlmNTU5YWI=
|
checksums.yaml.gz.sig
ADDED
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Historyable
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
# Implement the required interface for Rails::Generators::Migration.
|
11
|
+
def self.next_migration_number(dirname)
|
12
|
+
if ActiveRecord::Base.timestamped_migrations
|
13
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
14
|
+
else
|
15
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_migration_file
|
20
|
+
migration_template 'migration.rb', 'db/migrate/create_changes.rb'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateChanges < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :changes do |t|
|
4
|
+
t.references :item, polymorphic: true
|
5
|
+
t.string :object_attribute
|
6
|
+
t.text :object_attribute_value
|
7
|
+
t.datetime :created_at
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :changes, [:item_type, :item_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def down
|
14
|
+
drop_table :changes
|
15
|
+
end
|
16
|
+
end
|
data/lib/historyable.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require "historyable/version"
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "historyable/change"
|
5
|
+
|
6
|
+
module Historyable
|
7
|
+
class Item < Struct.new(:attribute, :association_name); end
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :historyable_items, instance_writer: false
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
# @param attributes [Array, Symbol]
|
19
|
+
def has_history(*attributes)
|
20
|
+
self.historyable_items = attributes.map do |attribute|
|
21
|
+
attribute = attribute.to_sym
|
22
|
+
association_name = attribute.to_s.insert(-1, '_changes').to_sym
|
23
|
+
|
24
|
+
Historyable::Item.new(attribute, association_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Associations
|
28
|
+
historyable_items.each do |historyable|
|
29
|
+
has_many historyable.association_name,
|
30
|
+
as: :item,
|
31
|
+
class_name: '::Change',
|
32
|
+
dependent: :destroy
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Instance methods
|
37
|
+
historyable_items.each do |historyable|
|
38
|
+
|
39
|
+
# raw_attribute_history
|
40
|
+
#
|
41
|
+
# @return [ActiveRecord::Relation]
|
42
|
+
define_method("raw_#{historyable.attribute.to_s}_history") do
|
43
|
+
send(historyable.association_name)
|
44
|
+
.where(object_attribute: historyable.attribute)
|
45
|
+
.order('created_at DESC')
|
46
|
+
.select(:object_attribute_value, :created_at)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# attribute_history
|
51
|
+
#
|
52
|
+
# @return [Array]
|
53
|
+
define_method("#{historyable.attribute.to_s}_history") do
|
54
|
+
unless instance_variable_get("@#{historyable.attribute.to_s}_history".to_sym)
|
55
|
+
array = []
|
56
|
+
|
57
|
+
records = send("raw_#{historyable.attribute}_history")
|
58
|
+
.pluck(:object_attribute_value, :created_at)
|
59
|
+
records.map do |attribute_value, created_at|
|
60
|
+
hash = HashWithIndifferentAccess.new
|
61
|
+
hash[:attribute_value] = attribute_value
|
62
|
+
hash[:changed_at] = created_at
|
63
|
+
|
64
|
+
array << hash
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets attribute_history cache
|
68
|
+
instance_variable_set("@#{historyable.attribute.to_s}_history".to_sym, array)
|
69
|
+
array
|
70
|
+
else
|
71
|
+
instance_variable_get("@#{historyable.attribute.to_s}_history".to_sym)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Callbacks
|
78
|
+
around_save :save_changes
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Creates a Change record when an attribute marked as 'historyable' changes
|
86
|
+
def save_changes
|
87
|
+
changed_historyable_items = historyable_items.select do |historyable|
|
88
|
+
changed.include?(historyable.attribute.to_s)
|
89
|
+
end
|
90
|
+
|
91
|
+
yield # saves the records
|
92
|
+
|
93
|
+
changed_historyable_items.each do |historyable|
|
94
|
+
send(historyable.association_name).create(object_attribute: historyable.attribute,
|
95
|
+
object_attribute_value: send(historyable.attribute))
|
96
|
+
# Expires attribute_history cache
|
97
|
+
instance_variable_set("@#{historyable.attribute.to_s}_history".to_sym, nil)
|
98
|
+
end
|
99
|
+
|
100
|
+
true
|
101
|
+
end
|
102
|
+
end
|
data.tar.gz.sig
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: historyable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philippe Dionne
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- !binary |-
|
12
|
+
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURmRENDQW1TZ0F3SUJB
|
13
|
+
Z0lCQVRBTkJna3Foa2lHOXcwQkFRVUZBREJDTVJRd0VnWURWUVFEREF0a2FX
|
14
|
+
OXUKYm1VdWNHaHBiREVWTUJNR0NnbVNKb21UOGl4a0FSa1dCV2R0WVdsc01S
|
15
|
+
TXdFUVlLQ1pJbWlaUHlMR1FCR1JZRApZMjl0TUI0WERURXpNRE13T1RFek1q
|
16
|
+
TXpOVm9YRFRFME1ETXdPVEV6TWpNek5Wb3dRakVVTUJJR0ExVUVBd3dMClpH
|
17
|
+
bHZibTVsTG5Cb2FXd3hGVEFUQmdvSmtpYUprL0lzWkFFWkZnVm5iV0ZwYkRF
|
18
|
+
VE1CRUdDZ21TSm9tVDhpeGsKQVJrV0EyTnZiVENDQVNJd0RRWUpLb1pJaHZj
|
19
|
+
TkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMdEdLMk5vRnlWWApzZUtsZkhi
|
20
|
+
OG51aEJ5OHJ3MW5QM0pWSFdveXFhbExzMHQzYXhVV2U2c0RDZ1FYQU9MM0Z0
|
21
|
+
a2ZZUFlseVg2WDRzCkZnZ2NTeVNVdDBQdkdkYXMweW0vbFcrc2ZHVDZnWERC
|
22
|
+
S2JQdmtXRk50cFhhSUo4RkxocW5KUjJtNTdiOFI2cGYKVk1CRGthOTVOODBY
|
23
|
+
dTlOUXJBZXNGK3dXL2M2WmlBT04yVkRzV2FiWkVmajE0Nnk3WnZ2aUI3cFpI
|
24
|
+
bFB6bkk0VwpHVmZPazBNQ2wwUTYwSnlrN1d3TmVjK05BOXJHN3ZFL2RXZEdP
|
25
|
+
S2Z4VTZaSm5DNmRyQzNrQnNCS2tSWnMrR1JoCjZYb0NBc3JLYlJ3Yi9aSTNB
|
26
|
+
MVMvSjl5dnEvTlExQi9VM0Npc0JGaHQrQ2Jkc3BlWFZVRHg1ZGZmRFZ4bnhk
|
27
|
+
by8KdzJSM3dKTXZEMk1DQXdFQUFhTjlNSHN3Q1FZRFZSMFRCQUl3QURBTEJn
|
28
|
+
TlZIUThFQkFNQ0JMQXdIUVlEVlIwTwpCQllFRkErNjlkR3h2RnRMeEorYUg4
|
29
|
+
bDRVTzl5eTg1M01DQUdBMVVkRVFRWk1CZUJGV1JwYjI1dVpTNXdhR2xzClFH
|
30
|
+
ZHRZV2xzTG1OdmJUQWdCZ05WSFJJRUdUQVhnUlZrYVc5dWJtVXVjR2hwYkVC
|
31
|
+
bmJXRnBiQzVqYjIwd0RRWUoKS29aSWh2Y05BUUVGQlFBRGdnRUJBRDdWcHV5
|
32
|
+
bTBWdm5YT3lkb0NCa3Yvdzh1MzJOamlndDNiRjNpL1A5MTEwOAphRHZPUlV5
|
33
|
+
bVlyUHhhQmUrQXNTSlVrSkIwTUh5d0pkTWM0TUYzWUlTZWxDdlVGR3phOExZ
|
34
|
+
dnZVSWd5VktGb20rClROOFdDUFpMbzRtWWFZbzRzU3RrRi9HNXY4STNydVpP
|
35
|
+
YnI3by9mMjB4RzVyOEU0TkQyR0VHVGVGSmhncE1jSE8KSXIxdVluL0xMZUdH
|
36
|
+
NWcyRUFLQ2RZeTJYb29odGNTUFlBcGJlRlFnV09HS29pb1FWUkNERzNjSG1h
|
37
|
+
a2ZWQVY1TQpEdU5senZqaFd0UER3RTVtWU81eDVYcXVXUXVFTnc3OHVydDFh
|
38
|
+
aW9OckllMC8xNWRIcERvSUVESUxhNnpVNDZCClhEdHAxWXhkZVZHSUJ1Tm9Q
|
39
|
+
MXZqRFN2TktZajBwTWpSQUVQcnFLMzlqS0U9Ci0tLS0tRU5EIENFUlRJRklD
|
40
|
+
QVRFLS0tLS0K
|
41
|
+
date: 2013-08-21 00:00:00.000000000 Z
|
42
|
+
dependencies:
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
prerelease: false
|
45
|
+
name: active_record
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 3.0.0
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 3.0.0
|
56
|
+
type: :runtime
|
57
|
+
description: Tracks model attributes changes.
|
58
|
+
email:
|
59
|
+
- philippe.dionne@hooktstudios.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- lib/generators/historyable/USAGE
|
65
|
+
- lib/generators/historyable/install_generator.rb
|
66
|
+
- lib/generators/historyable/templates/migration.rb
|
67
|
+
- lib/historyable.rb
|
68
|
+
- lib/historyable/change.rb
|
69
|
+
- lib/historyable/version.rb
|
70
|
+
homepage: https://github.com/hooktstudios/historyable
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project: ! '[none]'
|
90
|
+
rubygems_version: 2.0.3
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Tracks model attributes changes.
|
94
|
+
test_files: []
|
95
|
+
has_rdoc:
|
metadata.gz.sig
ADDED
Binary file
|