mongoid-history 0.6.0 → 0.6.1

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: 1fd1d2a3abaccb54ecc0c83da09b16dbaa7d8210
4
- data.tar.gz: 1126ed19a46d4542a059c2434665436043787424
3
+ metadata.gz: d92239564a7df075b3a55eb68c9f7f22666e24ef
4
+ data.tar.gz: 13f5bb15f7102d5b0957b569cb880af4249a0c75
5
5
  SHA512:
6
- metadata.gz: d83f26cbfe246d76d2659414b7a7feb48b1a27007cafec7bf1c4d957568b688375ce09302f0ed3a15329d31e87a4e15147212c70f3fa4796d6de783e17edfd0d
7
- data.tar.gz: 27fce1d91542b620f197f213185b570f057508a929f05abc8b2647cfeeeef543e536850e3127d9b29e567f032bc94a2d587a72c9d8f91facec0254632540b49e
6
+ metadata.gz: e9b09271720419ebbd843f78954d182430e4a3e5f020cc6b835993154c08fa55e6c8627beaef64f8813632ce15118186057d39771ddb863cf40f28a68c6ce7d3
7
+ data.tar.gz: 8d337e3008f6468a42b01c0babafa649685237f686e255bd620d380bf8d91bf77d56888445ca20b043ee6f2e7398d4821709fb877bafe11765948fc38ee6ddad
@@ -64,6 +64,7 @@ Style/Documentation:
64
64
  - 'spec/unit/singleton_methods_spec.rb'
65
65
  - 'spec/unit/trackable_spec.rb'
66
66
  - 'spec/unit/tracker_spec.rb'
67
+ - 'spec/unit/attributes/base_spec.rb'
67
68
  - 'spec/unit/attributes/create_spec.rb'
68
69
  - 'spec/unit/attributes/destroy_spec.rb'
69
70
  - 'spec/unit/attributes/update_spec.rb'
@@ -85,3 +86,7 @@ Style/MultilineBlockChain:
85
86
  Exclude:
86
87
  - 'lib/mongoid/history/trackable.rb'
87
88
  - 'lib/mongoid/history/tracker.rb'
89
+
90
+ Style/ClassLength:
91
+ Exclude:
92
+ - 'lib/mongoid/history/options.rb'
@@ -1,57 +1,57 @@
1
- 0.6.0 (2016/09/13)
2
- ------------------
1
+ ### 0.6.1 (2017/01/04)
3
2
 
4
- * [#2](https://github.com/mongoid/mongoid-history/pull/2): Froked into the [mongoid](https://github.com/mongoid) organization - [@dblock](https://github.com/dblock).
5
- * [#1](https://github.com/mongoid/mongoid-history/pull/1): Added Danger, PR linter - [@dblock](https://github.com/dblock).
6
- * [#166](https://github.com/aq1018/mongoid-history/pull/166): Hash fields should default to an empty Hash - [@johnnyshields](https://github.com/johnnyshields).
7
- * [#162](https://github.com/aq1018/mongoid-history/pull/162): Do not consider embedded relations as dynamic fields - [@JagdeepSingh](https://github.com/JagdeepSingh).
8
- * [#144](https://github.com/aq1018/mongoid-history/pull/158): Can modify history tracker insertion on object creation - [@sivagollapalli](https://github.com/sivagollapalli).
9
- * [#155](https://github.com/aq1018/mongoid-history/pull/155): Add support to whitelist the attributes for tracked embeds_one and embeds_many relations - [@JagdeepSingh](https://github.com/JagdeepSingh).
10
- * [#154](https://github.com/aq1018/mongoid-history/pull/154): Prevent soft-deleted embedded documents from tracking - [@JagdeepSingh](https://github.com/JagdeepSingh).
11
- * [#151](https://github.com/aq1018/mongoid-history/pull/151): Added ability to customize tracker class for each trackable; multiple trackers across the app are now possible - [@JagdeepSingh](https://github.com/JagdeepSingh).
12
- * [#151](https://github.com/aq1018/mongoid-history/pull/151): Added automatic support for `request_store` gem as drop-in replacement for `Thread.current` - [@JagdeepSingh](https://github.com/JagdeepSingh).
13
- * [#150](https://github.com/aq1018/mongoid-history/pull/150): Added support for keeping embedded objects audit history in parent itself - [@JagdeepSingh](https://github.com/JagdeepSingh).
3
+ * [#182](https://github.com/mongoid/mongoid-history/pull/182): No-op on repeated calls to destroy - [@msaffitz](https://github.com/msaffitz).
4
+ * [#170](https://github.com/mongoid/mongoid-history/pull/170): Parent repo is now [mongoid/mongoid-history](https://github.com/mongoid/mongoid-history) - [@dblock](https://github.com/dblock).
5
+ * [#171](https://github.com/mongoid/mongoid-history/pull/171): Add field formatting - [@jnfeinstein](https://github.com/jnfeinstein).
6
+ * [#172](https://github.com/mongoid/mongoid-history/pull/172): Add config helper to track all embedded relations - [@jnfeinstein](https://github.com/jnfeinstein).
7
+ * [#173](https://github.com/mongoid/mongoid-history/pull/173): Compatible with mongoid 6 - [@sivagollapalli](https://github.com/sivagollapalli).
14
8
 
15
- 0.5.0 (2015/09/18)
16
- ------------------
9
+ ### 0.6.0 (2016/09/13)
17
10
 
18
- * [#143](https://github.com/aq1018/mongoid-history/pull/143): Added support for Mongoid 5 - [@dblock](https://github.com/dblock).
19
- * [#133](https://github.com/aq1018/mongoid-history/pull/133): Added dynamic attributes tracking (Mongoid::Attributes::Dynamic) - [@minisai](https://github.com/minisai).
20
- * [#142](https://github.com/aq1018/mongoid-history/pull/142): Allow non-database fields to be specified in conjunction with a custom changes method - [@kayakyakr](https://github.com/kayakyakr).
11
+ * [#2](https://github.com/dblock/mongoid-history/pull/2): Forked into the [mongoid](https://github.com/mongoid) organization - [@dblock](https://github.com/dblock).
12
+ * [#1](https://github.com/dblock/mongoid-history/pull/1): Added Danger, PR linter - [@dblock](https://github.com/dblock).
13
+ * [#166](https://github.com/mongoid/mongoid-history/pull/166): Hash fields should default to an empty Hash - [@johnnyshields](https://github.com/johnnyshields).
14
+ * [#162](https://github.com/mongoid/mongoid-history/pull/162): Do not consider embedded relations as dynamic fields - [@JagdeepSingh](https://github.com/JagdeepSingh).
15
+ * [#144](https://github.com/mongoid/mongoid-history/pull/158): Can modify history tracker insertion on object creation - [@sivagollapalli](https://github.com/sivagollapalli).
16
+ * [#155](https://github.com/mongoid/mongoid-history/pull/155): Add support to whitelist the attributes for tracked embeds_one and embeds_many relations - [@JagdeepSingh](https://github.com/JagdeepSingh).
17
+ * [#154](https://github.com/mongoid/mongoid-history/pull/154): Prevent soft-deleted embedded documents from tracking - [@JagdeepSingh](https://github.com/JagdeepSingh).
18
+ * [#151](https://github.com/mongoid/mongoid-history/pull/151): Added ability to customize tracker class for each trackable; multiple trackers across the app are now possible - [@JagdeepSingh](https://github.com/JagdeepSingh).
19
+ * [#151](https://github.com/mongoid/mongoid-history/pull/151): Added automatic support for `request_store` gem as drop-in replacement for `Thread.current` - [@JagdeepSingh](https://github.com/JagdeepSingh).
20
+ * [#150](https://github.com/mongoid/mongoid-history/pull/150): Added support for keeping embedded objects audit history in parent itself - [@JagdeepSingh](https://github.com/JagdeepSingh).
21
21
 
22
- 0.4.7 (2015/04/06)
23
- ------------------
22
+ ### 0.5.0 (2015/09/18)
24
23
 
25
- * [#124](https://github.com/aq1018/mongoid-history/pull/124): You can require both `mongoid-history` and `mongoid/history` - [@dblock](https://github.com/dblock).
24
+ * [#143](https://github.com/mongoid/mongoid-history/pull/143): Added support for Mongoid 5 - [@dblock](https://github.com/dblock).
25
+ * [#133](https://github.com/mongoid/mongoid-history/pull/133): Added dynamic attributes tracking (Mongoid::Attributes::Dynamic) - [@minisai](https://github.com/minisai).
26
+ * [#142](https://github.com/mongoid/mongoid-history/pull/142): Allow non-database fields to be specified in conjunction with a custom changes method - [@kayakyakr](https://github.com/kayakyakr).
26
27
 
27
- 0.4.5 (2015/02/09)
28
- ------------------
28
+ ### 0.4.7 (2015/04/06)
29
29
 
30
- * [#131](https://github.com/aq1018/mongoid-history/pull/131): Added `undo` method, that helps to get specific version of an object without saving changes - [@alexkravets](https://github.com/alexkravets).
31
- * [#127](https://github.com/aq1018/mongoid-history/pull/127): Fixed gem naming per [rubygems](http://guides.rubygems.org/name-your-gem/) specs, now you can `require 'mongoid/history'` - [@nofxx](https://github.com/nofxx).
32
- * [#129](https://github.com/aq1018/mongoid-history/pull/129): Support multiple levels of embedded polimorphic documents - [@BrunoChauvet](https://github.com/BrunoChauvet).
33
- * [#123](https://github.com/aq1018/mongoid-history/pull/123): Used a method compatible with mongoid-observers to determinine the version of Mongoid - [@zeitnot](https://github.com/zeitnot).
30
+ * [#124](https://github.com/mongoid/mongoid-history/pull/124): You can require both `mongoid-history` and `mongoid/history` - [@dblock](https://github.com/dblock).
34
31
 
35
- 0.4.4 (2014/7/29)
36
- -----------------
32
+ ### 0.4.5 (2015/02/09)
37
33
 
38
- * [#111](https://github.com/aq1018/mongoid-history/pull/111): Fixed compatibility of `undo!` and `redo!` methods with Rails 3.x - [@mrjlynch](https://github.com/mrjlynch).
34
+ * [#131](https://github.com/mongoid/mongoid-history/pull/131): Added `undo` method, that helps to get specific version of an object without saving changes - [@alexkravets](https://github.com/alexkravets).
35
+ * [#127](https://github.com/mongoid/mongoid-history/pull/127): Fixed gem naming per [rubygems](http://guides.rubygems.org/name-your-gem/) specs, now you can `require 'mongoid/history'` - [@nofxx](https://github.com/nofxx).
36
+ * [#129](https://github.com/mongoid/mongoid-history/pull/129): Support multiple levels of embedded polimorphic documents - [@BrunoChauvet](https://github.com/BrunoChauvet).
37
+ * [#123](https://github.com/mongoid/mongoid-history/pull/123): Used a method compatible with mongoid-observers to determinine the version of Mongoid - [@zeitnot](https://github.com/zeitnot).
39
38
 
40
- 0.4.3 (2014/07/10)
41
- ------------------
39
+ ### 0.4.4 (2014/7/29)
42
40
 
43
- * [#110](https://github.com/aq1018/mongoid-history/pull/110): Fixed scope reference on history tracks criteria - [@adbeelitamar](https://github.com/adbeelitamar).
41
+ * [#111](https://github.com/mongoid/mongoid-history/pull/111): Fixed compatibility of `undo!` and `redo!` methods with Rails 3.x - [@mrjlynch](https://github.com/mrjlynch).
44
42
 
45
- 0.4.2 (2014/07/01)
46
- ------------------
43
+ ### 0.4.3 (2014/07/10)
47
44
 
48
- * [#106](https://github.com/aq1018/mongoid-history/pull/106): Added support for polymorphic relationship `scope` - [@adbeelitamar](https://github.com/adbeelitamar).
49
- * [#106](https://github.com/aq1018/mongoid-history/pull/106): Enabled specifying an array of relationships in `scope` - [@adbeelitamar](https://github.com/adbeelitamar).
50
- * [#83](https://github.com/aq1018/mongoid-history/pull/83): Added support for Mongoid 4.x, which removed `attr_accessible` in favor of protected attributes - [@dblock](https://github.com/dblock).
51
- * [#103](https://github.com/aq1018/mongoid-history/pull/103): Fixed compatibility with models using `default_scope` - [@mrjlynch](https://github.com/mrjlynch).
45
+ * [#110](https://github.com/mongoid/mongoid-history/pull/110): Fixed scope reference on history tracks criteria - [@adbeelitamar](https://github.com/adbeelitamar).
52
46
 
53
- 0.4.1 (2014/01/11)
54
- ------------------
47
+ ### 0.4.2 (2014/07/01)
48
+
49
+ * [#106](https://github.com/mongoid/mongoid-history/pull/106): Added support for polymorphic relationship `scope` - [@adbeelitamar](https://github.com/adbeelitamar).
50
+ * [#106](https://github.com/mongoid/mongoid-history/pull/106): Enabled specifying an array of relationships in `scope` - [@adbeelitamar](https://github.com/adbeelitamar).
51
+ * [#83](https://github.com/mongoid/mongoid-history/pull/83): Added support for Mongoid 4.x, which removed `attr_accessible` in favor of protected attributes - [@dblock](https://github.com/dblock).
52
+ * [#103](https://github.com/mongoid/mongoid-history/pull/103): Fixed compatibility with models using `default_scope` - [@mrjlynch](https://github.com/mrjlynch).
53
+
54
+ ### 0.4.1 (2014/01/11)
55
55
 
56
56
  * Fixed compatibility with Mongoid 4.x - [@dblock](https://github.com/dblock).
57
57
  * `Mongoid::History::Sweeper` has been removed, in accorance with Mongoid 4.x (see [#3108](https://github.com/mongoid/mongoid/issues/3108)) and Rails 4.x observer deprecation - [@dblock](https://github.com/dblock).
@@ -62,8 +62,7 @@
62
62
  * Replace Jeweler with Gem-Release - [@johnnyshields](https://github.com/johnnyshields).
63
63
  * Track version as a Ruby file - [@johnnyshields](https://github.com/johnnyshields).
64
64
 
65
- 0.4.0 (2013/06/12)
66
- ------------------
65
+ ### 0.4.0 (2013/06/12)
67
66
 
68
67
  * Added `Mongoid::History.disable` and `Mongoid::History.enabled?` methods for global tracking disablement - [@johnnyshields](https://github.com/johnnyshields).
69
68
  * Added `:changes_method` that optionally overrides which method to call to collect changes - [@joelnordel](https://github.com/joelnordell).
@@ -74,67 +73,58 @@
74
73
  * Added `#tracked_changes` and `#tracked_edits` methods to `Tracker` class for nicer change summaries - [@johnnyshields](https://github.com/johnnyshields) and [@tstepp](https://github.com/tstepp).
75
74
  * Refactored and exposed `#trackable_parent_class` in `Tracker`, which returns the class of the trackable regardless of whether the trackable itself has been destroyed - [@johnnyshields](https://github.com/johnnyshields).
76
75
  * Added class-level `#tracked_field?` and `#tracked_fields` methods; refactor logic to determine whether a field is tracked - [@johnnyshields](https://github.com/johnnyshields).
77
- * Fixed bug in Trackable#track_update where `return` condition at beginning of method caused a short-circuit where memoization would not be cleared properly. - [@johnnyshields](https://github.com/johnnyshields).
76
+ * Fixed bug in Trackable#track_update where `return` condition at beginning of method caused a short-circuit where memoization would not be cleared properly - [@johnnyshields](https://github.com/johnnyshields).
78
77
  * Tests: Added spec for nested embedded documents - [@matekb](https://github.com/matekb).
79
78
  * Tests: Test run time cut in half (~2.5s versus ~5s) by using `#let` helper and removing class initialization before each test - [@johnnyshields](https://github.com/johnnyshields).
80
79
  * Tests: Remove `database_cleaner` gem in favor of `Mongoid.purge!` - [@johnnyshields](https://github.com/johnnyshields).
81
80
  * Tests: Remove dependency on non-committed file `mongoid.yml` and hardcode collection to `mongoid_history_test` - [@johnnyshields](https://github.com/johnnyshields).
82
81
 
83
- 0.3.3 (2013/04/01)
84
- ------------------
82
+ ### 0.3.3 (2013/04/01)
85
83
 
86
- * [#42](https://github.com/aq1018/mongoid-history/issues/42): Fix: corrected creation of association chain when using nested embedded documents - [@matekb](https://github.com/matekb).
87
- * [#56](https://github.com/aq1018/mongoid-history/issues/56): Fix: now possible to undo setting (creating) attributes that was previously unset - [@matekb](https://github.com/matekb).
88
- * [#49](https://github.com/aq1018/mongoid-history/issues/49): Fix: now correctly undo/redo localized fields - [@edejong](https://github.com/edejong).
84
+ * [#42](https://github.com/mongoid/mongoid-history/issues/42): Fix: corrected creation of association chain when using nested embedded documents - [@matekb](https://github.com/matekb).
85
+ * [#56](https://github.com/mongoid/mongoid-history/issues/56): Fix: now possible to undo setting (creating) attributes that was previously unset - [@matekb](https://github.com/matekb).
86
+ * [#49](https://github.com/mongoid/mongoid-history/issues/49): Fix: now correctly undo/redo localized fields - [@edejong](https://github.com/edejong).
89
87
 
90
88
 
91
- 0.3.2 (2013/01/24)
92
- ------------------
89
+ ### 0.3.2 (2013/01/24)
93
90
 
94
- * [#54](https://github.com/aq1018/mongoid-history/pull/54): Used an index instead of the `$elemMatch` selector in `history_tracks` - [@vecio](https://github.com/vecio).
95
- * [#11](https://github.com/aq1018/mongoid-history/issues/11): Added `:modifier_field_inverse_of` on `track_history` that defines the `:inverse_of` relationship of the modifier class - [@matekb](https://github.com/matekb), [@dblock](https://github.com/dblock).
91
+ * [#54](https://github.com/mongoid/mongoid-history/pull/54): Used an index instead of the `$elemMatch` selector in `history_tracks` - [@vecio](https://github.com/vecio).
92
+ * [#11](https://github.com/mongoid/mongoid-history/issues/11): Added `:modifier_field_inverse_of` on `track_history` that defines the `:inverse_of` relationship of the modifier class - [@matekb](https://github.com/matekb), [@dblock](https://github.com/dblock).
96
93
 
97
- 0.3.1 (2012/11/16)
98
- ------------------
94
+ ### 0.3.1 (2012/11/16)
99
95
 
100
- * [#45](https://github.com/aq1018/mongoid-history/pull/45): Fix: intermittent hash ordering issue with `history_tracks` - [@getaroom](https://github.com/getaroom).
101
- * [#50](https://github.com/aq1018/mongoid-history/pull/50): Fix: tracking of array changes, undo and redo of field changes on non-embedded objects - [@dblock](https://github.com/dblock).
96
+ * [#45](https://github.com/mongoid/mongoid-history/pull/45): Fix: intermittent hash ordering issue with `history_tracks` - [@getaroom](https://github.com/getaroom).
97
+ * [#50](https://github.com/mongoid/mongoid-history/pull/50): Fix: tracking of array changes, undo and redo of field changes on non-embedded objects - [@dblock](https://github.com/dblock).
102
98
 
103
- 0.3.0 (2012/08/21)
104
- ------------------
99
+ ### 0.3.0 (2012/08/21)
105
100
 
106
- * [#41](https://github.com/aq1018/mongoid-history/pull/41): Mongoid 3.x support - [@zambot](https://github.com/zambot).
101
+ * [#41](https://github.com/mongoid/mongoid-history/pull/41): Mongoid 3.x support - [@zambot](https://github.com/zambot).
107
102
 
108
- 0.2.4 (2012/08/21)
109
- ------------------
103
+ ### 0.2.4 (2012/08/21)
110
104
 
111
- * [#38](https://github.com/aq1018/mongoid-history/pull/38): Fix: allow sub-models to be tracked by using `collection_name` as the scope - [@acant](https://github.com/acant).
112
- * [#35](https://github.com/aq1018/mongoid-history/pull/35): Fix: sweeper references record of change, not the record changed - [@dblock](https://github.com/dblock).
105
+ * [#38](https://github.com/mongoid/mongoid-history/pull/38): Fix: allow sub-models to be tracked by using `collection_name` as the scope - [@acant](https://github.com/acant).
106
+ * [#35](https://github.com/mongoid/mongoid-history/pull/35): Fix: sweeper references record of change, not the record changed - [@dblock](https://github.com/dblock).
113
107
 
114
- 0.2.3 (2012/04/20)
115
- ------------------
108
+ ### 0.2.3 (2012/04/20)
116
109
 
117
- * [#23](https://github.com/aq1018/mongoid-history/pull/34): Updated `Trackable::association_hash` to write through parent - [@tcopple](https://github.com/tcopple).
110
+ * [#23](https://github.com/mongoid/mongoid-history/pull/34): Updated `Trackable::association_hash` to write through parent - [@tcopple](https://github.com/tcopple).
118
111
  * Fix: `Trackable::association_hash` nil meta value call - [@tcopple](https://github.com/tcopple).
119
- * [#27](https://github.com/aq1018/mongoid-history/pull/27): Added support for re-creation of destroyed embedded documents - [@erlingwl](https://github.com/erlingwl).
112
+ * [#27](https://github.com/mongoid/mongoid-history/pull/27): Added support for re-creation of destroyed embedded documents - [@erlingwl](https://github.com/erlingwl).
120
113
 
121
- 0.1.7 (2011/12/09)
122
- ------------------
114
+ ### 0.1.7 (2011/12/09)
123
115
 
124
116
  * Fix: tracking `false` values - [@gottfrois](https://github.com/gottfrois).
125
117
  * Used a mongoid observer and controller `around_filter` to pick up modifying user from controller - [@bensymonds](https://github.com/bensymonds).
126
118
  * More flexible dependency on mongoid - [@sarcilav](https://github.com/sarcilav).
127
119
  * Fix: tracking broken in a multithreaded environment - [@dblock](https://github.com/dblock).
128
120
 
129
- 0.1.0 (2011/05/13)
130
- ------------------
121
+ ### 0.1.0 (2011/05/13)
131
122
 
132
123
  * Added support for `destroy` - [@dblock](https://github.com/dblock).
133
124
  * Added undo and redo - [@aq1018](https://github.com/aq1018).
134
125
  * Added support for temporarily disabling history tracking - [@aq1018](https://github.com/aq1018).
135
126
  * Record modifier for undo and redo actions - [@aq1018](https://github.com/aq1018).
136
127
 
137
- 0.0.1 (2011/03/04)
138
- ------------------
128
+ ### 0.0.1 (2011/03/04)
139
129
 
140
130
  * Intial public release - [@aq1018](https://github.com/aq1018).
data/Gemfile CHANGED
@@ -2,11 +2,11 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- case version = ENV['MONGOID_VERSION'] || '~> 6.0.0.rc0'
5
+ case version = ENV['MONGOID_VERSION'] || '~> 6.0.0'
6
6
  when 'HEAD'
7
7
  gem 'mongoid', github: 'mongodb/mongoid'
8
8
  when /6/
9
- gem 'mongoid', '~> 6.0.0.rc0'
9
+ gem 'mongoid', '~> 6.0.0'
10
10
  when /5/
11
11
  gem 'mongoid', '~> 5.0'
12
12
  gem 'mongoid-observers', '~> 0.2.0'
@@ -20,7 +20,7 @@ else
20
20
  end
21
21
 
22
22
  group :development, :test do
23
- gem 'rake'
23
+ gem 'rake', '< 11.0'
24
24
  gem 'bundler'
25
25
  end
26
26
 
data/README.md CHANGED
@@ -14,7 +14,7 @@ This gem also implements multi-user undo, which allows users to undo any history
14
14
  Install
15
15
  -------
16
16
 
17
- This gem supports Mongoid 3, 4 and 5 on Ruby 1.9.3 or newer. Add it to your `Gemfile` or run `gem install mongoid-history`.
17
+ This gem supports Mongoid 3, 4, 5 on Ruby 1.9.3 or newer and Mongoid 6 on Ruby 2.2.2+. Add it to your `Gemfile` or run `gem install mongoid-history`.
18
18
 
19
19
  ```ruby
20
20
  gem 'mongoid-history'
@@ -156,6 +156,35 @@ Mongoid::History.disable do
156
156
  end
157
157
  ```
158
158
 
159
+ You may want to track changes on all fields.
160
+
161
+ ```ruby
162
+ class Post
163
+ include Mongoid::Document
164
+ include Mongoid::History::Trackable
165
+
166
+ field :title
167
+ field :body
168
+ field :rating
169
+
170
+ track_history :on => [:fields] # all fields will be tracked
171
+ end
172
+ ```
173
+
174
+ You can also track changes on all embedded relations.
175
+
176
+ ```ruby
177
+ class Post
178
+ include Mongoid::Document
179
+ include Mongoid::History::Trackable
180
+
181
+ embeds_many :comments
182
+ embeds_one :content
183
+
184
+ track_history :on => [:embedded_relations] # all embedded relations will be tracked
185
+ end
186
+ ```
187
+
159
188
  **Include embedded objects attributes in parent audit**
160
189
 
161
190
  Modify above `Post` and `Comment` classes as below:
@@ -279,6 +308,41 @@ end
279
308
 
280
309
  This will skip the `page` documents with `removed_at` set to a non-blank value from nested tracking
281
310
 
311
+ **Formatting fields**
312
+
313
+ You can opt to use a proc or string interpolation to alter attributes being stored on a history record.
314
+
315
+ ```ruby
316
+ class Post
317
+ include Mongoid::Document
318
+ include Mongoid::History::Trackable
319
+
320
+ field :title
321
+ track_history on: :title,
322
+ format: { title: ->(t){ t[0..3] } }
323
+ ```
324
+
325
+ This also works for fields on an embedded relations.
326
+
327
+ ```ruby
328
+ class Book
329
+ include Mongoid::Document
330
+ include Mongoid::History::Trackable
331
+
332
+ embeds_many :pages
333
+ track_history on: :pages,
334
+ format: { pages: { number: 'pg. %d' } }
335
+ end
336
+
337
+ class Page
338
+ include Mongoid::Document
339
+ include Mongoid::History::Trackable
340
+
341
+ field :number, type: Integer
342
+ embedded_in :book
343
+ end
344
+ ```
345
+
282
346
  **Displaying history trackers as an audit trail**
283
347
 
284
348
  In your Controller:
@@ -341,7 +405,7 @@ Since October 2013 (mongoid-history version 0.4.1 and onwards), Mongoid::History
341
405
  instructions above then run the following command:
342
406
 
343
407
  ```
344
- MyHistoryTracker.all.each{|ht| ht.rename(:modifier_id, :created_by)
408
+ MyHistoryTracker.all.rename(modifier_id: :created_by)
345
409
  ```
346
410
 
347
411
  **Setting Modifier Class Name**
@@ -25,6 +25,47 @@ module Mongoid
25
25
  def changes
26
26
  trackable.send(changes_method)
27
27
  end
28
+
29
+ def format_field(field, value)
30
+ format_value(value, trackable_class.field_format(field))
31
+ end
32
+
33
+ def format_embeds_one_relation(rel, obj)
34
+ rel = trackable_class.database_field_name(rel)
35
+ relation_class = trackable_class.embeds_one_class(rel)
36
+ permitted_attrs = trackable_class.tracked_embeds_one_attributes(rel)
37
+ formats = trackable_class.field_format(rel)
38
+ format_relation(relation_class, obj, permitted_attrs, formats)
39
+ end
40
+
41
+ def format_embeds_many_relation(rel, obj)
42
+ rel = trackable_class.database_field_name(rel)
43
+ relation_class = trackable_class.embeds_many_class(rel)
44
+ permitted_attrs = trackable_class.tracked_embeds_many_attributes(rel)
45
+ formats = trackable_class.field_format(rel)
46
+ format_relation(relation_class, obj, permitted_attrs, formats)
47
+ end
48
+
49
+ def format_relation(relation_class, obj, permitted_attrs, formats)
50
+ obj.inject({}) do |m, field_value|
51
+ field = relation_class.database_field_name(field_value.first)
52
+ next m unless permitted_attrs.include?(field)
53
+
54
+ value = field_value.last
55
+ value = format_value(field_value.last, formats[field]) if formats.class == Hash
56
+ m.merge(field => value)
57
+ end
58
+ end
59
+
60
+ def format_value(value, format)
61
+ if format.class == String
62
+ format % value
63
+ elsif format.respond_to?(:call)
64
+ format.call(value)
65
+ else
66
+ value
67
+ end
68
+ end
28
69
  end
29
70
  end
30
71
  end
@@ -11,7 +11,7 @@ module Mongoid
11
11
  else
12
12
  v
13
13
  end
14
- @attributes[k] = [nil, modified]
14
+ @attributes[k] = [nil, format_field(k, modified)]
15
15
  end
16
16
  insert_embeds_one_changes
17
17
  insert_embeds_many_changes
@@ -25,11 +25,10 @@ module Mongoid
25
25
  rel_class = trackable_class.embeds_one_class(rel)
26
26
  paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
27
27
  paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
28
- permitted_attrs = trackable_class.tracked_embeds_one_attributes(rel)
29
28
  rel = aliased_fields.key(rel) || rel
30
29
  obj = trackable.send(rel)
31
30
  next if !obj || (obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present?)
32
- @attributes[rel] = [nil, obj.attributes.slice(*permitted_attrs)]
31
+ @attributes[rel] = [nil, format_embeds_one_relation(rel, obj.attributes)]
33
32
  end
34
33
  end
35
34
 
@@ -38,12 +37,11 @@ module Mongoid
38
37
  rel_class = trackable_class.embeds_many_class(rel)
39
38
  paranoia_field = Mongoid::History.trackable_class_settings(rel_class)[:paranoia_field]
40
39
  paranoia_field = rel_class.aliased_fields.key(paranoia_field) || paranoia_field
41
- permitted_attrs = trackable_class.tracked_embeds_many_attributes(rel)
42
40
  rel = aliased_fields.key(rel) || rel
43
41
  @attributes[rel] = [nil,
44
42
  trackable.send(rel)
45
43
  .reject { |obj| obj.respond_to?(paranoia_field) && obj.public_send(paranoia_field).present? }
46
- .map { |obj| obj.attributes.slice(*permitted_attrs) }]
44
+ .map { |obj| format_embeds_many_relation(rel, obj.attributes) }]
47
45
  end
48
46
  end
49
47
  end
@@ -4,7 +4,7 @@ module Mongoid
4
4
  class Destroy < ::Mongoid::History::Attributes::Base
5
5
  def attributes
6
6
  @attributes = {}
7
- trackable.attributes.each { |k, v| @attributes[k] = [v, nil] if trackable_class.tracked_field?(k, :destroy) }
7
+ trackable.attributes.each { |k, v| @attributes[k] = [format_field(k, v), nil] if trackable_class.tracked_field?(k, :destroy) }
8
8
  insert_embeds_one_changes
9
9
  insert_embeds_many_changes
10
10
  @attributes
@@ -16,9 +16,8 @@ module Mongoid
16
16
  trackable_class.tracked_embeds_one
17
17
  .map { |rel| aliased_fields.key(rel) || rel }
18
18
  .each do |rel|
19
- permitted_attrs = trackable_class.tracked_embeds_one_attributes(rel)
20
19
  obj = trackable.send(rel)
21
- @attributes[rel] = [obj.attributes.slice(*permitted_attrs), nil] if obj
20
+ @attributes[rel] = [format_embeds_one_relation(rel, obj.attributes), nil] if obj
22
21
  end
23
22
  end
24
23
 
@@ -26,8 +25,7 @@ module Mongoid
26
25
  trackable_class.tracked_embeds_many
27
26
  .map { |rel| aliased_fields.key(rel) || rel }
28
27
  .each do |rel|
29
- permitted_attrs = trackable_class.tracked_embeds_many_attributes(rel)
30
- @attributes[rel] = [trackable.send(rel).map { |obj| obj.attributes.slice(*permitted_attrs) }, nil]
28
+ @attributes[rel] = [trackable.send(rel).map { |obj| format_embeds_many_relation(rel, obj.attributes) }, nil]
31
29
  end
32
30
  end
33
31
  end
@@ -10,7 +10,7 @@ module Mongoid
10
10
  elsif trackable_class.tracked_embeds_many?(k)
11
11
  insert_embeds_many_changes(k, v)
12
12
  elsif trackable_class.tracked?(k, :update)
13
- @attributes[k] = v
13
+ @attributes[k] = format_field(k, v)
14
14
  end
15
15
  end
16
16
  @attributes
@@ -20,22 +20,22 @@ module Mongoid
20
20
 
21
21
  def insert_embeds_one_changes(relation, value)
22
22
  relation = trackable_class.database_field_name(relation)
23
- permitted_attrs = trackable_class.tracked_embeds_one_attributes(relation)
24
23
  relation_class = trackable_class.embeds_one_class(relation)
25
24
  paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
26
25
  @attributes[relation] = []
27
- @attributes[relation][0] = value[0][paranoia_field].present? ? {} : value[0].slice(*permitted_attrs)
28
- @attributes[relation][1] = value[1][paranoia_field].present? ? {} : value[1].slice(*permitted_attrs)
26
+ @attributes[relation][0] = value[0][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[0])
27
+ @attributes[relation][1] = value[1][paranoia_field].present? ? {} : format_embeds_one_relation(relation, value[1])
29
28
  end
30
29
 
31
30
  def insert_embeds_many_changes(relation, value)
32
31
  relation = trackable_class.database_field_name(relation)
33
- permitted_attrs = trackable_class.tracked_embeds_many_attributes(relation)
34
32
  relation_class = trackable_class.embeds_many_class(relation)
35
33
  paranoia_field = Mongoid::History.trackable_class_settings(relation_class)[:paranoia_field]
36
34
  @attributes[relation] = []
37
- @attributes[relation][0] = value[0].reject { |rel| rel[paranoia_field].present? }.map { |v_attrs| v_attrs.slice(*permitted_attrs) }
38
- @attributes[relation][1] = value[1].reject { |rel| rel[paranoia_field].present? }.map { |v_attrs| v_attrs.slice(*permitted_attrs) }
35
+ @attributes[relation][0] = value[0].reject { |rel| rel[paranoia_field].present? }
36
+ .map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
37
+ @attributes[relation][1] = value[1].reject { |rel| rel[paranoia_field].present? }
38
+ .map { |v_attrs| format_embeds_many_relation(relation, v_attrs) }
39
39
  end
40
40
  end
41
41
  end
@@ -14,6 +14,7 @@ module Mongoid
14
14
  def parse(options = {})
15
15
  @options = default_options.merge(options)
16
16
  prepare_skipped_fields
17
+ prepare_formatted_fields
17
18
  parse_tracked_fields_and_relations
18
19
  @options
19
20
  end
@@ -31,7 +32,8 @@ module Mongoid
31
32
  scope: scope,
32
33
  track_create: false,
33
34
  track_update: true,
34
- track_destroy: false }
35
+ track_destroy: false,
36
+ format: nil }
35
37
  end
36
38
 
37
39
  # Sets the :except attributes and relations in `options` to be an [ Array <String> ]
@@ -43,6 +45,30 @@ module Mongoid
43
45
  @options[:except] = options[:except].map { |field| trackable.database_field_name(field) }.compact.uniq
44
46
  end
45
47
 
48
+ def prepare_formatted_fields
49
+ formats = {}
50
+
51
+ if options[:format].class == Hash
52
+ options[:format].each do |field, format|
53
+ next if field.nil?
54
+
55
+ field = trackable.database_field_name(field)
56
+
57
+ if format.class == Hash && trackable.embeds_many?(field)
58
+ relation_class = trackable.embeds_many_class(field)
59
+ formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
60
+ elsif format.class == Hash && trackable.embeds_one?(field)
61
+ relation_class = trackable.embeds_one_class(field)
62
+ formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
63
+ else
64
+ formats[field] = format
65
+ end
66
+ end
67
+ end
68
+
69
+ options[:format] = formats
70
+ end
71
+
46
72
  def parse_tracked_fields_and_relations
47
73
  # case `options[:on]`
48
74
  # when `posts: [:id, :title]`, then it will convert it to `[[:posts, [:id, :title]]]`
@@ -64,6 +90,11 @@ module Mongoid
64
90
  @options[:on] = options[:on] | trackable.fields.keys.map(&:to_sym) - reserved_fields.map(&:to_sym)
65
91
  end
66
92
 
93
+ if options[:on].include?(:embedded_relations)
94
+ @options[:on] = options[:on].reject { |opt| opt == :embedded_relations }
95
+ @options[:on] = options[:on] | trackable.embedded_relations.keys
96
+ end
97
+
67
98
  @options[:fields] = []
68
99
  @options[:dynamic] = []
69
100
  @options[:relations] = { embeds_one: {}, embeds_many: {} }
@@ -241,7 +241,7 @@ module Mongoid
241
241
  end
242
242
 
243
243
  def track_destroy
244
- track_history_for_action(:destroy)
244
+ track_history_for_action(:destroy) unless destroyed?
245
245
  end
246
246
 
247
247
  def clear_trackable_memoization
@@ -387,6 +387,10 @@ module Mongoid
387
387
  !embedded_relations.map { |_, v| v.key }.include?(database_field_name(field))
388
388
  end
389
389
 
390
+ def field_format(field)
391
+ field_formats[database_field_name(field)]
392
+ end
393
+
390
394
  # Retrieves the list of tracked fields for a given action.
391
395
  #
392
396
  # @param [ String | Symbol ] action The action name (:create, :update, or :destroy)
@@ -413,6 +417,10 @@ module Mongoid
413
417
  @reserved_tracked_fields ||= ['_id', history_trackable_options[:version_field].to_s, "#{history_trackable_options[:modifier_field]}_id"]
414
418
  end
415
419
 
420
+ def field_formats
421
+ @field_formats ||= history_trackable_options[:format]
422
+ end
423
+
416
424
  # Whether or not the relation should be tracked.
417
425
  #
418
426
  # @param [ String | Symbol ] relation The name of the relation
@@ -485,6 +493,7 @@ module Mongoid
485
493
  @tracked_fields = nil
486
494
  @tracked_embeds_one = nil
487
495
  @tracked_embeds_many = nil
496
+ @obfuscated_fields = nil
488
497
  end
489
498
  end
490
499
  end
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module History
3
- VERSION = '0.6.0'
3
+ VERSION = '0.6.1'
4
4
  end
5
5
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.summary = 'Track and audit, undo and redo changes on Mongoid documents.'
9
9
  s.description = 'This library tracks historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. Embedded documents are referenced by storing an association path, which is an array of document_name and document_id fields starting from the top most parent document and down to the embedded document that should track history. Mongoid-history implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but it is probably not suitable for use cases such as a wiki.'
10
10
  s.email = ['aq1018@gmail.com', 'justin.mgrimes@gmail.com', 'dblock@dblock.org']
11
- s.homepage = 'http://github.com/aq1018/mongoid-history'
11
+ s.homepage = 'http://github.com/mongoid/mongoid-history'
12
12
  s.license = 'MIT'
13
13
 
14
14
  s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
@@ -136,6 +136,13 @@ describe Mongoid::History do
136
136
  post.destroy
137
137
  expect(post.history_tracks.last.affected['title']).to eq('Test')
138
138
  end
139
+
140
+ it 'should no-op on repeated calls to destroy' do
141
+ post.destroy
142
+ expect do
143
+ post.destroy
144
+ end.not_to change(Tracker, :count)
145
+ end
139
146
  end
140
147
 
141
148
  describe 'on update non-embedded' do
@@ -13,6 +13,18 @@ describe Mongoid::History::Attributes::Base do
13
13
  end
14
14
  end
15
15
 
16
+ before :all do
17
+ class ModelTwo
18
+ include Mongoid::Document
19
+ field :foo
20
+ field :goo
21
+ end
22
+ end
23
+
24
+ after :all do
25
+ Object.send(:remove_const, :ModelTwo)
26
+ end
27
+
16
28
  let(:obj_one) { model_one.new }
17
29
  let(:base) { described_class.new(obj_one) }
18
30
  subject { base }
@@ -51,4 +63,88 @@ describe Mongoid::History::Attributes::Base do
51
63
  subject { base.send(:changes) }
52
64
  it { is_expected.to eq('foo' => ['Foo', 'Foo-new']) }
53
65
  end
66
+
67
+ describe '#format_field' do
68
+ before(:each) do
69
+ model_one.instance_variable_set(:@history_trackable_options, nil)
70
+ end
71
+
72
+ subject { base.send(:format_field, :bar, 'foo') }
73
+
74
+ context 'when formatted via string' do
75
+ before do
76
+ model_one.track_history format: { bar: '*%s*' }
77
+ end
78
+
79
+ it { is_expected.to eq '*foo*' }
80
+ end
81
+
82
+ context 'when formatted via proc' do
83
+ before do
84
+ model_one.track_history format: { bar: ->(v) { v * 2 } }
85
+ end
86
+
87
+ it { is_expected.to eq 'foofoo' }
88
+ end
89
+
90
+ context 'when not formatted' do
91
+ before do
92
+ model_one.track_history
93
+ end
94
+
95
+ it { is_expected.to eq 'foo' }
96
+ end
97
+ end
98
+
99
+ shared_examples 'formats embedded relation' do |relation_type|
100
+ let(:model_two) { ModelTwo.new(foo: :bar, goo: :baz) }
101
+
102
+ before :each do
103
+ model_one.instance_variable_set(:@history_trackable_options, nil)
104
+ model_one.send(relation_type, :model_two)
105
+ end
106
+
107
+ subject { base.send("format_#{relation_type}_relation", :model_two, model_two.attributes) }
108
+
109
+ context 'with permitted attributes' do
110
+ before do
111
+ model_one.track_history on: { model_two: %i(foo) }
112
+ end
113
+
114
+ it 'should select only permitted attributes' do
115
+ is_expected.to include('foo' => :bar)
116
+ is_expected.to_not include('goo')
117
+ end
118
+ end
119
+
120
+ context 'with attributes formatted via string' do
121
+ before do
122
+ model_one.track_history on: { model_two: %i(foo) }, format: { model_two: { foo: '&%s&' } }
123
+ end
124
+
125
+ it 'should select obfuscate permitted attributes' do
126
+ is_expected.to include('foo' => '&bar&')
127
+ is_expected.to_not include('goo')
128
+ end
129
+ end
130
+
131
+ context 'with attributes formatted via proc' do
132
+ before do
133
+ model_one.track_history on: { model_two: %i(foo) }, format: { model_two: { foo: ->(v) { v.to_s * 2 } } }
134
+ end
135
+
136
+ it 'should select obfuscate permitted attributes' do
137
+ is_expected.to include('foo' => 'barbar')
138
+ is_expected.to_not include('goo')
139
+ end
140
+ end
141
+ end
142
+
143
+ describe '#format_embeds_one_relation' do
144
+ include_examples 'formats embedded relation', :embeds_one
145
+ end
146
+
147
+ describe '#format_embeds_many_relation' do
148
+ include_examples 'formats embedded relation', :embeds_many
149
+ end
54
150
  end
@@ -69,7 +69,8 @@ describe Mongoid::History::Options do
69
69
  scope: :model_one,
70
70
  track_create: false,
71
71
  track_update: true,
72
- track_destroy: false }
72
+ track_destroy: false,
73
+ format: nil }
73
74
  end
74
75
  it { expect(service.send(:default_options)).to eq expected_options }
75
76
  end
@@ -104,6 +105,26 @@ describe Mongoid::History::Options do
104
105
  end
105
106
  end
106
107
 
108
+ describe '#prepare_formatted_fields' do
109
+ let(:options) { { format: value } }
110
+ subject { service.parse(options) }
111
+
112
+ context 'with non-hash' do
113
+ let(:value) { :foo }
114
+ it { expect(subject[:format]).to eq({}) }
115
+ end
116
+
117
+ context 'with a field format' do
118
+ let(:value) { { foo: '&&&' } }
119
+ it { expect(subject[:format]).to include 'foo' => '&&&' }
120
+ end
121
+
122
+ context 'with nested format' do
123
+ let(:value) { { emb_one: { f_em_foo: '***' } } }
124
+ it { expect(subject[:format]).to include 'emb_one' => { 'f_em_foo' => '***' } }
125
+ end
126
+ end
127
+
107
128
  describe '#parse_tracked_fields_and_relations' do
108
129
  context 'when options not passed' do
109
130
  let(:expected_options) do
@@ -119,7 +140,8 @@ describe Mongoid::History::Options do
119
140
  track_destroy: false,
120
141
  fields: %w(foo b),
121
142
  dynamic: [],
122
- relations: { embeds_one: {}, embeds_many: {} } }
143
+ relations: { embeds_one: {}, embeds_many: {} },
144
+ format: {} }
123
145
  end
124
146
  it { expect(service.parse).to eq expected_options }
125
147
  end
@@ -232,6 +254,16 @@ describe Mongoid::History::Options do
232
254
  let(:options) { { on: :my_field } }
233
255
  it { expect(subject[:dynamic]).to eq %w(my_field) }
234
256
  end
257
+
258
+ context 'with relations' do
259
+ let(:options) { { on: :embedded_relations } }
260
+ it do
261
+ expect(subject[:relations]).to eq(embeds_many: { 'emb_threes' => %w(_id f_em_foo fmb),
262
+ 'emfs' => %w(_id f_em_baz) },
263
+ embeds_one: { 'emb_one' => %w(_id f_em_foo fmb),
264
+ 'emtw' => %w(_id f_em_baz) })
265
+ end
266
+ end
235
267
  end
236
268
  end
237
269
 
@@ -46,7 +46,8 @@ describe Mongoid::History::Trackable do
46
46
  track_destroy: false,
47
47
  fields: %w(foo),
48
48
  relations: { embeds_one: {}, embeds_many: {} },
49
- dynamic: [] }
49
+ dynamic: [],
50
+ format: {} }
50
51
  end
51
52
  let(:regular_fields) { ['foo'] }
52
53
  let(:reserved_fields) { %w(_id version modifier_id) }
@@ -151,6 +152,38 @@ describe Mongoid::History::Trackable do
151
152
  end
152
153
  end
153
154
 
155
+ describe '#field_format' do
156
+ before :all do
157
+ ModelOne = Class.new do
158
+ include Mongoid::Document
159
+ include Mongoid::History::Trackable
160
+ field :foo
161
+ end
162
+ end
163
+
164
+ let(:format) { '***' }
165
+
166
+ before do
167
+ ModelOne.track_history format: { foo: format }
168
+ end
169
+
170
+ context 'when field is formatted' do
171
+ it 'should return the format' do
172
+ expect(ModelOne.field_format(:foo)).to be format
173
+ end
174
+ end
175
+
176
+ context 'when field is not formatted' do
177
+ it 'should return nil' do
178
+ expect(ModelOne.field_format(:bar)).to be_nil
179
+ end
180
+ end
181
+
182
+ after :all do
183
+ Object.send(:remove_const, :ModelOne)
184
+ end
185
+ end
186
+
154
187
  context 'sub-model' do
155
188
  before :each do
156
189
  class MySubModel < MyModel
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid-history
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Qian
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-09-13 00:00:00.000000000 Z
13
+ date: 2017-01-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: easy_diff
@@ -132,7 +132,7 @@ files:
132
132
  - spec/unit/store/request_store_spec.rb
133
133
  - spec/unit/trackable_spec.rb
134
134
  - spec/unit/tracker_spec.rb
135
- homepage: http://github.com/aq1018/mongoid-history
135
+ homepage: http://github.com/mongoid/mongoid-history
136
136
  licenses:
137
137
  - MIT
138
138
  metadata: {}