undo 0.1.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +131 -109
- data/VERSION +1 -0
- data/lib/undo.rb +12 -24
- data/lib/undo/config.rb +15 -22
- data/lib/undo/container/json.rb +15 -0
- data/lib/undo/integration/shared_undo_integration_examples.rb +31 -0
- data/lib/undo/keeper.rb +36 -0
- data/lib/undo/storage/adapter.rb +31 -0
- data/lib/undo/storage/memory.rb +6 -4
- data/lib/undo/version.rb +1 -1
- data/spec/spec_helper_lite.rb +1 -8
- data/spec/support/ci_helper.rb +16 -0
- data/spec/undo/config_spec.rb +10 -0
- data/spec/undo/container/json_spec.rb +16 -0
- data/spec/undo/serializer/null_spec.rb +11 -6
- data/spec/undo/storage/memory_spec.rb +17 -4
- data/spec/undo_spec.rb +26 -54
- data/undo.gemspec +6 -10
- metadata +32 -13
- data/lib/undo/wrapper.rb +0 -34
- data/spec/undo/wrapper_spec.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bbec1f206620c902a60106546a48d9b126a31d8
|
4
|
+
data.tar.gz: 1e0f6f864cc9bc39bbf80950e6b9071d4b436814
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e08e8aa4f48fe64d67651e07a6f305ef50f7192fbf8f75b06d26bbaf321fbc53d77b7130523719c7885efe9514627eee6691e82b1cbc7385ba4b2210ffa8f49
|
7
|
+
data.tar.gz: 3dbb5154581a0e24a443e9002d037b43648a542a73f92096018073af42114f16caa80083edb26519106877c51cf35f8c019e27eef55fff182295e2d515a2c0e3
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1.
|
1
|
+
2.1.1
|
data/README.md
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
Undo
|
2
2
|
==========
|
3
3
|
[](https://travis-ci.org/AlexParamonov/undo)
|
4
|
-
[](http://gemnasium.com/AlexParamonov/undo)
|
5
4
|
[](https://coveralls.io/r/AlexParamonov/undo?branch=master)
|
5
|
+
[](https://codeclimate.com/github/AlexParamonov/undo)
|
6
|
+
[](http://gemnasium.com/AlexParamonov/undo)
|
6
7
|
[](http://badge.fury.io/rb/undo)
|
7
8
|
|
8
|
-
Undo
|
9
|
-
It stores the object state before the
|
9
|
+
The Undo restores previous object state.
|
10
|
+
It stores the object state before the mutation and allows to
|
10
11
|
restore this state later.
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
Lightweight, modular and very flexible library that works as with
|
14
|
+
Rails, as with plain Ruby. No specific persistence required: store
|
15
|
+
data as it suites you.
|
16
|
+
|
17
|
+
The Undo uses plugable adapters for storage such as Redis,
|
18
|
+
RailsCache. And serializers such as ActiveModel for example. Simple
|
19
|
+
interface makes it easy to implement new adapters or tweak existing.
|
20
|
+
|
21
|
+
Most of adapters and serializers have no external dependencies and can
|
22
|
+
be used with similary quaking objects.
|
17
23
|
|
18
24
|
Contents
|
19
25
|
---------
|
@@ -23,11 +29,14 @@ Contents
|
|
23
29
|
1. Undo operation
|
24
30
|
1. Configuration options
|
25
31
|
1. In place configuration
|
32
|
+
1. Pass through options
|
33
|
+
1. Utils
|
26
34
|
1. Contacts
|
27
35
|
1. Compatibility
|
28
36
|
1. Contributing
|
29
37
|
1. Copyright
|
30
38
|
|
39
|
+
|
31
40
|
Installation
|
32
41
|
------------
|
33
42
|
|
@@ -43,146 +52,159 @@ Or install it yourself as:
|
|
43
52
|
|
44
53
|
$ gem install undo
|
45
54
|
|
55
|
+
|
46
56
|
Requirements
|
47
57
|
------------
|
48
58
|
1. Ruby 1.9 or above
|
49
|
-
1. gem virtus ~> 1.0
|
59
|
+
1. gem [virtus](https://github.com/solnic/virtus) ~> 1.0
|
60
|
+
|
50
61
|
|
51
62
|
Usage
|
52
63
|
-----
|
53
64
|
|
54
65
|
### Undo operation
|
55
66
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
uuid = Undo.store object
|
68
|
+
|
69
|
+
# ...
|
70
|
+
# do something destructive
|
71
|
+
# ...
|
72
|
+
|
73
|
+
Undo.restore uuid
|
74
|
+
|
60
75
|
That is basically it :)
|
61
76
|
|
62
|
-
|
63
|
-
|
64
|
-
``` ruby
|
65
|
-
decorated_object = Undo.wrap object
|
66
|
-
Undo.restore decorated_object.uuid
|
67
|
-
```
|
68
|
-
Use decorated_object as usual afterwards. Undo gem will store object
|
69
|
-
state on each hit to `mutator methods`. By default mutator_methods are
|
70
|
-
`update`, `delete`, `destroy`. Those methods can be changed either in
|
71
|
-
place or using global configuration (see below).
|
72
|
-
|
73
|
-
To use something more advanced rather plain memory storage and
|
74
|
-
pass through serializer, configure the Undo:
|
75
|
-
|
76
|
-
``` ruby
|
77
|
-
Undo.configure do |config|
|
78
|
-
config.storage = Undo::Storage::RailsCache.new
|
79
|
-
config.serializer = Undo::Serializer::ActiveModel.new
|
80
|
-
end
|
81
|
-
```
|
82
|
-
gem `undo-storage-rails_cache` and gem `undo-serializer-active_model` are required for this.
|
83
|
-
There are a bunch of other Rails free adapters. Read about them in configuration chapter below.
|
77
|
+
To use something more advanced than plain memory storage and
|
78
|
+
pass through serializer, configure the Undo:
|
84
79
|
|
85
|
-
|
80
|
+
Undo.configure do |config|
|
81
|
+
config.storage = Undo::Storage::RailsCache.new
|
82
|
+
config.serializer = Undo::Serializer::ActiveModel.new
|
83
|
+
end
|
84
|
+
|
85
|
+
It allows to serialize and deserialize ActiveRecord models (and
|
86
|
+
POROs after implementing the #attributes method) and store it to Rails
|
87
|
+
cache. See those gems documentation for more details:
|
88
|
+
|
89
|
+
* [gem `undo-storage-rails_cache`](https://github.com/AlexParamonov/undo-storage-rails_cache)
|
90
|
+
* [gem `undo-serializer-active_model`](https://github.com/AlexParamonov/undo-serializer-active_model)
|
91
|
+
|
92
|
+
There are more adapters. Read about them in configuration chapter below.
|
93
|
+
|
94
|
+
When storage does not garbage collect old records, call
|
86
95
|
|
87
|
-
|
88
|
-
setting (see below in configuration chapter).
|
96
|
+
Undo.delete uuid
|
89
97
|
|
90
|
-
|
98
|
+
to manually delete related data in storage. It accepts the same options as store/restore.
|
91
99
|
|
92
|
-
|
93
|
-
|
100
|
+
### UUID
|
101
|
+
|
102
|
+
UUID is a uniq key used by the Undo to store and retrieve data. It is
|
103
|
+
generated automatically, but it is possible to either provide custom
|
104
|
+
uuid or custom uuid generator.
|
94
105
|
|
95
|
-
|
96
|
-
Undo.
|
97
|
-
|
106
|
+
# use generated uuid
|
107
|
+
uuid = Undo.store object
|
108
|
+
Undo.restore uuid
|
98
109
|
|
99
|
-
|
110
|
+
# manually specify uuid
|
111
|
+
uuid = "uniq identifier"
|
112
|
+
Undo.store object, uuid: uuid
|
113
|
+
Undo.restore uuid
|
100
114
|
|
101
|
-
|
102
|
-
|
103
|
-
Undo.
|
104
|
-
|
105
|
-
|
115
|
+
# specify uuid generator in place
|
116
|
+
uuid = Undo.store object, uuid_generator: ->(object) { "#{object.class.name}_#{object.id}" }
|
117
|
+
Undo.restore uuid
|
118
|
+
|
119
|
+
By default uuids are generated by `SecureRandom.uuid`. The generator
|
120
|
+
set by `uuid_generator` option which may be set in place as shown
|
121
|
+
above or in global configuration (see below).
|
106
122
|
|
107
123
|
### Configuration options
|
108
124
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
125
|
+
#### Storage
|
126
|
+
|
127
|
+
The `storage` option is responsible of defining a storage adapter that
|
128
|
+
can store, fetch and delete object state from storage.
|
129
|
+
|
130
|
+
Adapter must implement `store(uuid, object, options)`, `fetch(uuid, options)` and
|
131
|
+
optionally `delete(uuid, options)` methods. Naming follows ruby `Hash`
|
132
|
+
interface.
|
133
|
+
|
134
|
+
Currently available storage adapters:
|
135
|
+
|
136
|
+
* [`Undo::Storage::Memory`](https://github.com/AlexParamonov/undo/blob/master/lib/undo/storage/memory.rb) is a simple runtime storage (ruby Hash object).
|
137
|
+
* [`gem "undo-storage-rails_cache"`](https://github.com/AlexParamonov/undo-storage-rails_cache) designed for Rails cache, but can be used with any similarly quaking cache store. Rails constant may not even be defined.
|
138
|
+
* [`gem "undo-storage-redis"`](https://github.com/AlexParamonov/undo-storage-redis) designed to be used with `gem "redis"`
|
139
|
+
|
140
|
+
Check the [documentation on Github](http://github.com/AlexParamonov/undo/README.md)
|
141
|
+
for current list of available storage adapters.
|
115
142
|
|
116
|
-
|
117
|
-
on project repository for full list of currently available storage adapters.
|
143
|
+
#### Serializer
|
118
144
|
|
119
|
-
To convert objects to
|
145
|
+
To convert objects to hashes that can be processed by storage adapters
|
146
|
+
and backward, `serializer` is used:
|
120
147
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
148
|
+
Undo.configure do |config|
|
149
|
+
config.serializer = CustomSerializer.new
|
150
|
+
end
|
151
|
+
|
152
|
+
Serializer must implement `serialize(object, options)` and
|
153
|
+
`deserialize(object, options)` methods.
|
126
154
|
|
127
155
|
Currently available serializers:
|
128
|
-
* `Undo::Serializer::Null` pass though serializer. It do nothing and returns whatever passed to it.
|
129
|
-
* `gem undo-serializer-active_model` depends on #attributes method so can be used with Virtus and PORO objects as well as with ActiveRecord objects.
|
130
156
|
|
131
|
-
|
132
|
-
|
157
|
+
* [`Undo::Serializer::Null`](https://github.com/AlexParamonov/undo/blob/master/lib/undo/serializer/null.rb) passes through object without any convertions. It do nothing and returns whatever passed to it.
|
158
|
+
* [`gem undo-serializer-active_model`](https://github.com/AlexParamonov/undo-serializer-active_model) allows to serialize the ActiveRecord models as well as POROs (with implemented #attributes method).
|
159
|
+
* [`gem undo-serializer-primitive`](https://github.com/AlexParamonov/undo-serializer-primitive) serializes primitive values. Is used by active_model serializer
|
133
160
|
|
134
|
-
|
161
|
+
Check the [documentation on Github](http://github.com/AlexParamonov/undo/README.md)
|
162
|
+
for current list of available serializers.
|
135
163
|
|
136
|
-
|
137
|
-
|
164
|
+
Serializer could return any type of object, Hash is not forced. But in
|
165
|
+
this case compatible storage adapter should be used.
|
138
166
|
|
139
|
-
|
140
|
-
Undo.configure do |config|
|
141
|
-
config.uuid_generator = ->(object) { "#{object.class.name}_#{object.id}" }
|
142
|
-
end
|
143
|
-
```
|
167
|
+
#### UUID generator
|
144
168
|
|
145
|
-
`
|
146
|
-
By default mutator_methods are `update`, `delete`, `destroy`.
|
147
|
-
To append custom mutator_methods use
|
169
|
+
`uuid_generator` option allows to setup custom uuid generator:
|
148
170
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
end
|
153
|
-
```
|
171
|
+
Undo.configure do |config|
|
172
|
+
config.uuid_generator = ->(object) { "#{object.class.name}_#{object.id}" }
|
173
|
+
end
|
154
174
|
|
155
|
-
|
175
|
+
By default it is using `SecureRandom.uuid`.
|
156
176
|
|
157
|
-
|
158
|
-
place for given operation. To restore object from another storage use
|
159
|
-
`storage` option:
|
177
|
+
### In place configuration
|
160
178
|
|
161
|
-
|
162
|
-
|
163
|
-
```
|
179
|
+
Any configuration option from previous chapter can be applied in place
|
180
|
+
for a given operation.
|
164
181
|
|
165
|
-
|
182
|
+
For example to restore object from another storage, the `storage`
|
183
|
+
option may be used in place:
|
166
184
|
|
167
|
-
|
168
|
-
Undo.wrap object, mutator_methods: [:save]
|
169
|
-
```
|
185
|
+
Undo.restore uuid, storage: AnotherStorage.new
|
170
186
|
|
171
187
|
To use custom serializer or deserializer use `serializer` option:
|
172
188
|
|
173
|
-
|
174
|
-
Undo.
|
175
|
-
|
176
|
-
|
177
|
-
|
189
|
+
Undo.store post, serializer: PostSerializer.new(post)
|
190
|
+
Undo.restore uuid, serializer: PostDeserializer.new(options)
|
191
|
+
|
192
|
+
and so on.
|
193
|
+
|
194
|
+
### Pass through options
|
195
|
+
|
196
|
+
Any option, that is not recognized by the Undo as configuration
|
197
|
+
option, will be bypass to the serializer and storage adapter:
|
198
|
+
|
199
|
+
Undo.store post, include: :comments, expires_in: 1.hour
|
178
200
|
|
179
|
-
|
180
|
-
`#serialize` or `#deserialize`, those options can be set in place as well:
|
201
|
+
Same applies for `#restore` and `#delete` methods.
|
181
202
|
|
182
|
-
|
183
|
-
Undo.store post, include: :comments
|
184
|
-
```
|
203
|
+
### Utils
|
185
204
|
|
205
|
+
* [`Undo::Container::Json`](https://github.com/AlexParamonov/undo/blob/master/lib/undo/container/json.rb) used to pack and unpack hashes in storages
|
206
|
+
* [`Undo::Storage::Adapter`](https://github.com/AlexParamonov/undo/blob/master/lib/undo/storage/adapter.rb) abstract storage adapter that provides pack and unpack methods
|
207
|
+
* [Shared integration test](https://github.com/AlexParamonov/undo/blob/master/lib/undo/integration/shared_undo_integration_examples.rb) can be used in storage or serializers to verify integrity with Undo gem versions
|
186
208
|
|
187
209
|
Contacts
|
188
210
|
-------------
|
@@ -207,11 +229,11 @@ See [build history](http://travis-ci.org/#!/AlexParamonov/undo/builds)
|
|
207
229
|
|
208
230
|
## Contributing
|
209
231
|
|
210
|
-
1. Fork repository
|
211
|
-
2. Create
|
212
|
-
3. Commit
|
232
|
+
1. [Fork repository](http://github.com/AlexParamonov/undo/fork)
|
233
|
+
2. Create feature branch (`git checkout -b my-new-feature`)
|
234
|
+
3. Commit changes (`git commit -am 'Add some feature'`)
|
213
235
|
4. Push to the branch (`git push origin my-new-feature`)
|
214
|
-
5. Create new Pull Request
|
236
|
+
5. Create new Pull Request on Github
|
215
237
|
|
216
238
|
Copyright
|
217
239
|
---------
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/undo.rb
CHANGED
@@ -1,44 +1,32 @@
|
|
1
1
|
require "undo/version"
|
2
2
|
require "undo/config"
|
3
|
-
require "undo/
|
3
|
+
require "undo/keeper"
|
4
4
|
|
5
5
|
module Undo
|
6
|
-
def self.configure
|
7
|
-
|
6
|
+
def self.configure
|
7
|
+
yield config
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.store(object, options = {})
|
11
|
-
|
12
|
-
uuid(object, options).tap do |uuid|
|
13
|
-
config.storage.put uuid,
|
14
|
-
config.serializer.serialize(object, config.filter(options))
|
15
|
-
end
|
16
|
-
end
|
11
|
+
keeper(options).store object
|
17
12
|
end
|
18
13
|
|
19
14
|
def self.restore(uuid, options = {})
|
20
|
-
|
21
|
-
config.serializer.deserialize config.storage.fetch(uuid),
|
22
|
-
config.filter(options)
|
23
|
-
|
24
|
-
end
|
15
|
+
keeper(options).restore uuid
|
25
16
|
end
|
26
17
|
|
27
|
-
def self.
|
28
|
-
options
|
29
|
-
config.with(options) do |config|
|
30
|
-
Wrapper.new object, options.merge(mutator_methods: config.mutator_methods)
|
31
|
-
end
|
18
|
+
def self.delete(uuid, options = {})
|
19
|
+
keeper(options).delete uuid
|
32
20
|
end
|
33
21
|
|
34
22
|
private
|
35
|
-
def self.
|
36
|
-
|
23
|
+
def self.config
|
24
|
+
@config ||= Config.new
|
37
25
|
end
|
38
26
|
|
39
|
-
def self.
|
40
|
-
|
27
|
+
def self.keeper(options)
|
28
|
+
Keeper.new(config, options)
|
41
29
|
end
|
42
30
|
|
43
|
-
private_class_method :
|
31
|
+
private_class_method :config, :keeper
|
44
32
|
end
|
data/lib/undo/config.rb
CHANGED
@@ -1,35 +1,28 @@
|
|
1
1
|
require "virtus"
|
2
|
+
require "undo/serializer/null"
|
3
|
+
require "undo/storage/memory"
|
4
|
+
require "securerandom"
|
2
5
|
|
3
6
|
module Undo
|
4
7
|
class Config
|
5
8
|
include Virtus.model
|
6
9
|
|
7
|
-
attribute :
|
10
|
+
attribute :serializer, Object, default: Undo::Serializer::Null.new
|
11
|
+
attribute :storage, Object, default: Undo::Storage::Memory.new
|
12
|
+
attribute :uuid_generator, Proc, default: :default_uuid_generator
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
->(object) { SecureRandom.uuid }
|
12
|
-
}
|
13
|
-
attribute :serializer, Object, default: ->(config, _) {
|
14
|
-
require "undo/serializer/null"
|
15
|
-
Undo::Serializer::Null.new
|
16
|
-
}
|
17
|
-
attribute :storage, Object, default: ->(config, _) {
|
18
|
-
require "undo/storage/memory"
|
19
|
-
Undo::Storage::Memory.new serializer: config.serializer
|
20
|
-
}, lazy: true
|
21
|
-
|
22
|
-
def with(attribute_updates = {}, &block)
|
23
|
-
config = attribute_updates.empty? ? self
|
24
|
-
: self.class.new(attribute_set.get(self).merge attribute_updates)
|
25
|
-
|
26
|
-
block_given? ? block.call(config) : config
|
14
|
+
def with(attribute_updates = {})
|
15
|
+
self.class.new attributes.merge attribute_updates
|
27
16
|
end
|
28
17
|
|
29
18
|
def filter(options)
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
attribute_names = attribute_set.map(&:name)
|
20
|
+
options.reject { |key, _| attribute_names.include? key.to_sym }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def default_uuid_generator
|
25
|
+
-> object { SecureRandom.uuid }
|
33
26
|
end
|
34
27
|
end
|
35
28
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
shared_examples "undo integration" do
|
2
|
+
subject { Undo }
|
3
|
+
let(:object) { Hash.new hello: :world }
|
4
|
+
|
5
|
+
it "stores and restores object" do
|
6
|
+
uuid = subject.store object
|
7
|
+
expect(subject.restore uuid).to eq object
|
8
|
+
end
|
9
|
+
|
10
|
+
it "deletes stored object" do
|
11
|
+
uuid = subject.store object
|
12
|
+
subject.delete uuid
|
13
|
+
expect { subject.restore uuid }.to raise_error(KeyError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "delete an unexisted key does not raise an error" do
|
17
|
+
expect { subject.delete "does not exist" }.not_to raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "special cases" do
|
21
|
+
it "stores and restores nil" do
|
22
|
+
uuid = subject.store nil
|
23
|
+
expect(subject.restore uuid).to eq nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "stores and restores array" do
|
27
|
+
uuid = subject.store [1,2,3]
|
28
|
+
expect(subject.restore uuid).to eq [1,2,3]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/undo/keeper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Undo
|
4
|
+
class Keeper
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize(config, options)
|
8
|
+
@config = config.with options
|
9
|
+
@options = config.filter options
|
10
|
+
end
|
11
|
+
|
12
|
+
def store(object)
|
13
|
+
build_uuid(object).tap do |uuid|
|
14
|
+
reflection = serializer.serialize object, options
|
15
|
+
storage.store uuid, reflection, options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def restore(uuid)
|
20
|
+
reflection = storage.fetch uuid, options
|
21
|
+
serializer.deserialize reflection, options
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(uuid)
|
25
|
+
storage.delete uuid, options
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
attr_reader :config, :options
|
30
|
+
def_delegators :config, :storage, :serializer, :uuid_generator
|
31
|
+
|
32
|
+
def build_uuid(object)
|
33
|
+
options[:uuid] || uuid_generator.call(object)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "undo/container/json"
|
2
|
+
|
3
|
+
module Undo
|
4
|
+
module Storage
|
5
|
+
class Adapter
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options
|
8
|
+
@container = options.fetch(:container) { Undo::Container::Json.new }
|
9
|
+
end
|
10
|
+
|
11
|
+
def store(uuid, object, options = {}) end
|
12
|
+
def fetch(uuid, options = {}) end
|
13
|
+
def delete(uuid, options = {}) end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :options, :container
|
17
|
+
|
18
|
+
def pack(object)
|
19
|
+
container.pack object
|
20
|
+
end
|
21
|
+
|
22
|
+
def unpack(data)
|
23
|
+
container.unpack data
|
24
|
+
end
|
25
|
+
|
26
|
+
def adapter_options(local_options)
|
27
|
+
options.merge local_options
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/undo/storage/memory.rb
CHANGED
@@ -4,17 +4,19 @@ module Undo
|
|
4
4
|
def initialize(options = {})
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def store(uuid, object, options = {})
|
8
8
|
storage[uuid] = object
|
9
9
|
end
|
10
10
|
|
11
|
-
def fetch(uuid)
|
11
|
+
def fetch(uuid, options = {})
|
12
12
|
storage.fetch(uuid)
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
def delete(uuid, options = {})
|
16
|
+
storage.delete(uuid)
|
17
|
+
end
|
17
18
|
|
19
|
+
private
|
18
20
|
def storage
|
19
21
|
@storage ||= {}
|
20
22
|
end
|
data/lib/undo/version.rb
CHANGED
data/spec/spec_helper_lite.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
if !!ENV["CI"]
|
2
|
+
require 'simplecov'
|
3
|
+
require 'coveralls'
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
]
|
8
|
+
|
9
|
+
SimpleCov.start do
|
10
|
+
add_filter '/spec/'
|
11
|
+
add_filter '/vendor/'
|
12
|
+
minimum_coverage(90)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
require "pry"
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper_lite"
|
2
|
+
require "undo/container/json"
|
3
|
+
|
4
|
+
describe Undo::Container::Json do
|
5
|
+
let(:container) { described_class.new }
|
6
|
+
|
7
|
+
it "packs to json" do
|
8
|
+
json = container.pack "hello" => "world"
|
9
|
+
expect(json).to eq '{"hello":"world"}'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "unpaks from json" do
|
13
|
+
hash = container.unpack '{"hello":"world"}'
|
14
|
+
expect(hash).to eq "hello" => "world"
|
15
|
+
end
|
16
|
+
end
|
@@ -2,18 +2,23 @@ require "spec_helper_lite"
|
|
2
2
|
require "undo/serializer/null"
|
3
3
|
|
4
4
|
describe Undo::Serializer::Null do
|
5
|
-
|
6
|
-
let(:serializer) { described_class.new }
|
5
|
+
let(:object) { double :object }
|
7
6
|
|
8
7
|
describe "returns passed argument" do
|
9
|
-
let(:object) { double :object }
|
10
|
-
|
11
8
|
specify "#serialize" do
|
12
|
-
expect(
|
9
|
+
expect(subject.serialize object).to eq object
|
13
10
|
end
|
14
11
|
|
15
12
|
specify "#deserialize" do
|
16
|
-
expect(
|
13
|
+
expect(subject.deserialize object).to eq object
|
17
14
|
end
|
18
15
|
end
|
16
|
+
|
17
|
+
it "accepts options" do
|
18
|
+
options = { foo: :bar }
|
19
|
+
expect do
|
20
|
+
subject.serialize object, options
|
21
|
+
subject.deserialize object, options
|
22
|
+
end.not_to raise_error
|
23
|
+
end
|
19
24
|
end
|
@@ -2,12 +2,25 @@ require "spec_helper_lite"
|
|
2
2
|
require 'undo/storage/memory'
|
3
3
|
|
4
4
|
describe Undo::Storage::Memory do
|
5
|
-
subject { described_class }
|
6
|
-
let(:adapter) { subject.new }
|
7
5
|
let(:object) { double :object }
|
8
6
|
|
9
7
|
it "stores any object" do
|
10
|
-
|
11
|
-
expect(
|
8
|
+
subject.store 123, object
|
9
|
+
expect(subject.fetch 123).to eq object
|
10
|
+
end
|
11
|
+
|
12
|
+
it "deletes stored object" do
|
13
|
+
subject.store 123, object
|
14
|
+
subject.delete 123
|
15
|
+
expect { subject.fetch 123 }.to raise_error(KeyError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts options" do
|
19
|
+
options = { foo: :bar }
|
20
|
+
expect do
|
21
|
+
subject.store 123, object, options
|
22
|
+
subject.fetch 123, options
|
23
|
+
subject.delete 123, options
|
24
|
+
end.not_to raise_error
|
12
25
|
end
|
13
26
|
end
|
data/spec/undo_spec.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
require "spec_helper_lite"
|
2
|
+
require "undo/integration/shared_undo_integration_examples"
|
2
3
|
|
3
4
|
describe Undo do
|
4
5
|
let(:object) { double :object, change: true }
|
5
6
|
subject { described_class }
|
6
7
|
|
7
|
-
|
8
|
-
uuid = subject.store object
|
9
|
-
expect(subject.restore uuid).to eq object
|
10
|
-
end
|
8
|
+
include_examples "undo integration"
|
11
9
|
|
12
10
|
it "stores and restores object using provided uuid" do
|
13
11
|
uuid = "uniq_identifier"
|
@@ -16,13 +14,13 @@ describe Undo do
|
|
16
14
|
expect(subject.restore uuid).to eq object
|
17
15
|
end
|
18
16
|
|
19
|
-
describe "
|
20
|
-
let(:storage) { double :storage }
|
17
|
+
describe "serialization" do
|
21
18
|
let(:serializer) { double :serializer }
|
19
|
+
let(:storage) { double :storage }
|
22
20
|
|
23
21
|
it "serializes data before storing" do
|
24
22
|
expect(serializer).to receive(:serialize).with(object, anything).ordered
|
25
|
-
expect(storage).to receive(:
|
23
|
+
expect(storage).to receive(:store).ordered
|
26
24
|
|
27
25
|
subject.store object,
|
28
26
|
storage: storage,
|
@@ -30,17 +28,19 @@ describe Undo do
|
|
30
28
|
end
|
31
29
|
|
32
30
|
it "deserializes data before restoring" do
|
33
|
-
|
34
|
-
|
35
|
-
expect(storage).to receive(:fetch).and_return(foo: :bar).ordered
|
36
|
-
expect(serializer).to receive(:deserialize).with({ foo: :bar }, anything).ordered
|
31
|
+
expect(storage).to receive(:fetch).and_return(object).ordered
|
32
|
+
expect(serializer).to receive(:deserialize).with(object, anything).ordered
|
37
33
|
|
38
|
-
subject.restore uuid,
|
34
|
+
subject.restore "uuid",
|
39
35
|
storage: storage,
|
40
36
|
serializer: serializer
|
41
37
|
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "pass through options to serializer" do
|
41
|
+
let(:serializer) { double :serializer }
|
42
42
|
|
43
|
-
|
43
|
+
specify "from #store" do
|
44
44
|
expect(serializer).to receive(:serialize).with(object, foo: :bar)
|
45
45
|
|
46
46
|
subject.store object,
|
@@ -48,19 +48,7 @@ describe Undo do
|
|
48
48
|
foo: :bar
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
expect(serializer).to receive(:serialize)
|
53
|
-
|
54
|
-
wrapper = subject.wrap object,
|
55
|
-
serializer: serializer,
|
56
|
-
mutator_methods: :change,
|
57
|
-
foo: :bar
|
58
|
-
|
59
|
-
wrapper.change
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
it "pass through options from restore to deserialize" do
|
51
|
+
specify "from #restore" do
|
64
52
|
uuid = subject.store object
|
65
53
|
expect(serializer).to receive(:deserialize).with(object, foo: :bar)
|
66
54
|
|
@@ -70,39 +58,23 @@ describe Undo do
|
|
70
58
|
end
|
71
59
|
end
|
72
60
|
|
73
|
-
describe "
|
74
|
-
|
75
|
-
subject.configure do |config|
|
76
|
-
config.mutator_methods = [:change]
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
it "is a decorator" do
|
81
|
-
object = %w[hello world]
|
61
|
+
describe "pass through options to storage" do
|
62
|
+
let(:storage) { double :storage }
|
82
63
|
|
83
|
-
|
84
|
-
expect(
|
85
|
-
decorator.some_method
|
64
|
+
specify "from #store" do
|
65
|
+
expect(storage).to receive(:store).with(anything, object, foo: :bar)
|
86
66
|
|
87
|
-
|
88
|
-
|
67
|
+
subject.store object,
|
68
|
+
storage: storage,
|
69
|
+
foo: :bar
|
89
70
|
end
|
90
71
|
|
91
|
-
|
92
|
-
|
93
|
-
uuid = "uniq_identifier"
|
94
|
-
model = subject.wrap object, uuid: uuid
|
95
|
-
model.change
|
96
|
-
|
97
|
-
expect(subject.restore uuid).to eq object
|
98
|
-
end
|
72
|
+
specify "from #restore" do
|
73
|
+
expect(storage).to receive(:fetch).with("uuid", foo: :bar)
|
99
74
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
expect(subject.restore model.uuid).to eq object
|
105
|
-
end
|
75
|
+
subject.restore "uuid",
|
76
|
+
storage: storage,
|
77
|
+
foo: :bar
|
106
78
|
end
|
107
79
|
end
|
108
80
|
end
|
data/undo.gemspec
CHANGED
@@ -1,21 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'undo/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "undo"
|
8
|
-
spec.version =
|
7
|
+
spec.version = IO.read("VERSION")
|
9
8
|
spec.authors = ["Alexander Paramonov"]
|
10
9
|
spec.email = ["alexander.n.paramonov@gmail.com"]
|
11
10
|
spec.summary = %q{Reverts operation made upon object}
|
12
11
|
spec.description = %q{
|
13
|
-
Undo
|
14
|
-
|
15
|
-
|
16
|
-
Undo uses adapters for storage (Redis, ActiveRecord, etc) and custom serializers (ActiveRecord, Virtus, etc).
|
17
|
-
It is very lightweight solution that can be used as well with heavy ActiveRecord as with simple Hash or Virtus objects.
|
18
|
-
No database required: store data as it suites you.
|
12
|
+
The Undo restores previous object state.
|
13
|
+
Lightweight, modular and very flexible library that works as with
|
14
|
+
Rails, as with plain Ruby.
|
19
15
|
}
|
20
16
|
spec.homepage = "http://github.com/AlexParamonov/undo"
|
21
17
|
spec.license = "MIT"
|
@@ -26,8 +22,8 @@ No database required: store data as it suites you.
|
|
26
22
|
spec.require_paths = ["lib"]
|
27
23
|
|
28
24
|
spec.add_dependency "virtus", "~> 1.0"
|
29
|
-
spec.
|
25
|
+
spec.add_dependency "json", ">= 1.7"
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.0'
|
30
27
|
spec.add_development_dependency "rake"
|
31
28
|
spec.add_development_dependency "rspec", ">= 3.0.0.beta1"
|
32
|
-
|
33
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: undo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Paramonov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: virtus
|
@@ -24,20 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
47
|
+
version: '1.0'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
54
|
+
version: '1.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,12 +82,9 @@ dependencies:
|
|
68
82
|
version: 3.0.0.beta1
|
69
83
|
description: |2
|
70
84
|
|
71
|
-
Undo
|
72
|
-
|
73
|
-
|
74
|
-
Undo uses adapters for storage (Redis, ActiveRecord, etc) and custom serializers (ActiveRecord, Virtus, etc).
|
75
|
-
It is very lightweight solution that can be used as well with heavy ActiveRecord as with simple Hash or Virtus objects.
|
76
|
-
No database required: store data as it suites you.
|
85
|
+
The Undo restores previous object state.
|
86
|
+
Lightweight, modular and very flexible library that works as with
|
87
|
+
Rails, as with plain Ruby.
|
77
88
|
email:
|
78
89
|
- alexander.n.paramonov@gmail.com
|
79
90
|
executables: []
|
@@ -90,16 +101,22 @@ files:
|
|
90
101
|
- LICENSE.txt
|
91
102
|
- README.md
|
92
103
|
- Rakefile
|
104
|
+
- VERSION
|
93
105
|
- lib/undo.rb
|
94
106
|
- lib/undo/config.rb
|
107
|
+
- lib/undo/container/json.rb
|
108
|
+
- lib/undo/integration/shared_undo_integration_examples.rb
|
109
|
+
- lib/undo/keeper.rb
|
95
110
|
- lib/undo/serializer/null.rb
|
111
|
+
- lib/undo/storage/adapter.rb
|
96
112
|
- lib/undo/storage/memory.rb
|
97
113
|
- lib/undo/version.rb
|
98
|
-
- lib/undo/wrapper.rb
|
99
114
|
- spec/spec_helper_lite.rb
|
115
|
+
- spec/support/ci_helper.rb
|
116
|
+
- spec/undo/config_spec.rb
|
117
|
+
- spec/undo/container/json_spec.rb
|
100
118
|
- spec/undo/serializer/null_spec.rb
|
101
119
|
- spec/undo/storage/memory_spec.rb
|
102
|
-
- spec/undo/wrapper_spec.rb
|
103
120
|
- spec/undo_spec.rb
|
104
121
|
- undo.gemspec
|
105
122
|
homepage: http://github.com/AlexParamonov/undo
|
@@ -128,8 +145,10 @@ specification_version: 4
|
|
128
145
|
summary: Reverts operation made upon object
|
129
146
|
test_files:
|
130
147
|
- spec/spec_helper_lite.rb
|
148
|
+
- spec/support/ci_helper.rb
|
149
|
+
- spec/undo/config_spec.rb
|
150
|
+
- spec/undo/container/json_spec.rb
|
131
151
|
- spec/undo/serializer/null_spec.rb
|
132
152
|
- spec/undo/storage/memory_spec.rb
|
133
|
-
- spec/undo/wrapper_spec.rb
|
134
153
|
- spec/undo_spec.rb
|
135
154
|
has_rdoc:
|
data/lib/undo/wrapper.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
|
-
module Undo
|
4
|
-
class Wrapper < SimpleDelegator
|
5
|
-
extend Forwardable
|
6
|
-
def_delegators :object, :class, :kind_of?
|
7
|
-
attr_reader :uuid
|
8
|
-
|
9
|
-
def initialize(object, options = {})
|
10
|
-
@object = object
|
11
|
-
@mutator_methods = options.delete(:mutator_methods) || []
|
12
|
-
@uuid = object.respond_to?(:uuid) ? object.uuid : options.fetch(:uuid)
|
13
|
-
@options = options
|
14
|
-
|
15
|
-
super object
|
16
|
-
end
|
17
|
-
|
18
|
-
def method_missing(method, *args, &block)
|
19
|
-
store if mutator_methods.include? method
|
20
|
-
super method, *args, &block
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
attr_reader :object, :options
|
25
|
-
|
26
|
-
def mutator_methods
|
27
|
-
Kernel.Array(@mutator_methods)
|
28
|
-
end
|
29
|
-
|
30
|
-
def store
|
31
|
-
Undo.store object, options.merge(uuid: uuid)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/spec/undo/wrapper_spec.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require "spec_helper_lite"
|
2
|
-
|
3
|
-
describe Undo::Wrapper do
|
4
|
-
subject { described_class }
|
5
|
-
let(:model) { subject.new object, uuid: uuid, mutator_methods: mutator_methods }
|
6
|
-
let(:mutator_methods) { [:change] }
|
7
|
-
let(:object) { double :object, change: true }
|
8
|
-
let(:uuid) { double :uuid }
|
9
|
-
|
10
|
-
describe "storage" do
|
11
|
-
it "stores object when mutator method is called" do
|
12
|
-
expect(model).to receive(:store)
|
13
|
-
model.change
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "#uuid" do
|
18
|
-
it "uses provided uuid" do
|
19
|
-
expect(model.uuid).to eq uuid
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "when object respond_to uuid" do
|
23
|
-
it "uses object#uuid instead" do
|
24
|
-
expect(object).to receive(:uuid) { "123" }
|
25
|
-
expect(model.uuid).to eq "123"
|
26
|
-
expect(Undo).to receive(:store).with(object, hash_including(uuid: "123"))
|
27
|
-
model.change
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|