polaroid 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +1 -0
- data/lib/polaroid.rb +47 -0
- data/polaroid.gemspec +27 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bcc0c39513e0b9468561e2d3bcf13e917dda9fb1
|
4
|
+
data.tar.gz: 5f61fe411b563dc1bddef7389edda3684ad6eb78
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2eed4a6e80f15591b16bec0e972a53439957a44f1595c4dc5930116efcaa44d549c50895c37f8293fb8cc90c53e36d43196f02bc50956e536bf1e882f34496ed
|
7
|
+
data.tar.gz: 32aca9025f1217ad821a220534096465ccbcc697345e512dfa5aec3fda4ced875fe650dcb1c17eb6e0426f30acfd00aade004e402c046e2250599a6f9c0b3718
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Paul Kwiatkowski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Polaroid
|
2
|
+
|
3
|
+
Polaroid provides shortcuts to capture the state of a Ruby object, and can construct a fake object later to mimic that state.
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
Never send a Hash to do an Object's job. Method dispatch is faster and method calls look cleaner than nested\['braces'\]\['everywhere'\] in your code.
|
8
|
+
|
9
|
+
## But why would you ever need that?
|
10
|
+
|
11
|
+
The use case that inspired Polaroid involved using a background worker to send an Email. The email was being templated from a single View-Model object. Rather than shuffling database primary keys across a queue (be it Redis, or a real messaging system like Rabbit) and bothering the database later, we can just build the View-Model now, capture its state as JSON, and send that off to the Mailer.
|
12
|
+
|
13
|
+
But that requires the templates now access data from a Hash. And you should never send a Hash to do an Object's job.
|
14
|
+
|
15
|
+
Well, since we were already pulling these values from an object, why not just push them back into a Fake that shares the same interface of the View-Model? Or at least as much interface as we care about.
|
16
|
+
|
17
|
+
This is just one use case I have found.
|
18
|
+
|
19
|
+
## Simple Example
|
20
|
+
|
21
|
+
In a class of which you would like to have a Snapshot, include a new instance of Polaroid, passing it the list of messages you would like to capture. These are not limited to attributes, but can be any message name. It is highly encouraged that the chosen messages be idempotent and side-effect-free wherever possible.
|
22
|
+
|
23
|
+
An example of a Person class with two attributes, and a method which selects
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Person
|
27
|
+
include Polaroid.new(:name, :age, :favorite_drinks)
|
28
|
+
|
29
|
+
attr_reader :name, :age, :favorites
|
30
|
+
|
31
|
+
def initialize(name, age, favorites)
|
32
|
+
@name = name
|
33
|
+
@age = age
|
34
|
+
@favorites = favorites
|
35
|
+
end
|
36
|
+
|
37
|
+
def favorite_drinks
|
38
|
+
favorites.select { |fav| drink?(fav) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def favorite_foods
|
42
|
+
favorites.select { |fav| food?(fav) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def drink?(str)
|
46
|
+
%w[coffee beer wine tea water juice].include?(str)
|
47
|
+
end
|
48
|
+
|
49
|
+
def food?(str)
|
50
|
+
%w[omelete burrito ramen pie yogurt].include?(str)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
And if we make a `Person` instance, we can take a snapshot, which by default is just a `Hash`.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
pat = Person.new('Patrick', 25, ['beer', 'coffee', 'ramen', 'pie'])
|
59
|
+
# => #<Person @name="Patrick", @age=25, @favorites=["beer", "coffee", "ramen", "pie"]>
|
60
|
+
pat.age
|
61
|
+
# => 25
|
62
|
+
pat.favorite_drinks
|
63
|
+
# => ["beer", "coffee"]
|
64
|
+
pat.favorite_foods
|
65
|
+
# => ["ramen", "pie"]
|
66
|
+
snapshot = pat.take_shapshot
|
67
|
+
# => { name: "Patrick", age: 25, favorite_drinks: ["beer", "coffee"] }
|
68
|
+
```
|
69
|
+
|
70
|
+
But now we can take that snapshot Hash and build a fake Person (actual class is `Person::Snapshot`) that responds to all the messages we chose to record from the original `Person`:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
fake_pat = Person.build_from_snapshot(snapshot)
|
74
|
+
# => #<struct Person::Snapshot name="Patrick", age=25, favorite_drinks=["beer", "coffee"]>
|
75
|
+
fake_pat.age
|
76
|
+
# => 25
|
77
|
+
fake_pat.favorite_drinks
|
78
|
+
# => ["beer", "coffee"]
|
79
|
+
```
|
80
|
+
|
81
|
+
However, since we did not record the whole `favorites` array, nor did we ask the message `favorite_foods` be included our snapshot, the `Person::Snapshot` does not respond to `favorite_foods`:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
fake_pat.favorite_foods
|
85
|
+
# => NoMethodError: undefined method `favorite_foods' for #<Person::Snapshot>
|
86
|
+
```
|
87
|
+
|
88
|
+
## Hashes? But I want JSON!
|
89
|
+
|
90
|
+
Of course you do. So the `take_snapshot` and `build_from_snapshot` methods also take a trailing `format` parameter:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
pat = Person.new('Patrick', 25, ['beer', 'coffee', 'ramen', 'pie'])
|
94
|
+
json_snapshot = snapshot.take_snapshot(pat, :json)
|
95
|
+
# => "{\"name\":\"Patrick\",\"age\":25,\"favorite_drinks\":[\"beer\",\"coffee\"]}"
|
96
|
+
Person.build_from_snapshot(json_snapshot, :json)
|
97
|
+
# => #<struct Person::Snapshot name="Patrick", age=25, favorite_drinks=["beer", "coffee"]>
|
98
|
+
```
|
99
|
+
|
100
|
+
Right now the only accepted formats are `:hash` and `:json`. If there are other serialization formats that people care about, I would be happy to have them included.
|
101
|
+
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
1. Fork it ( http://github.com/swifthand/polaroid/fork )
|
106
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
107
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
108
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
109
|
+
5. Create new Pull Request
|
110
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/polaroid.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Polaroid < Module
|
2
|
+
|
3
|
+
VERSION = "0.0.3"
|
4
|
+
|
5
|
+
def initialize(*messages)
|
6
|
+
@messages = messages
|
7
|
+
@polaroid_struct_class = ImmutableStruct.new(*messages)
|
8
|
+
define_capture_method
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
# Build the fake class for internal use in the including class' namespace.
|
13
|
+
def included(base)
|
14
|
+
base.const_set(:Snapshot, @polaroid_struct_class)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
private #######################################################################
|
20
|
+
|
21
|
+
# Give the class a #take_snapshot instance method, which records the result
|
22
|
+
# of sending an instance the assigned messages, and returns them as a Hash.
|
23
|
+
def define_capture_method
|
24
|
+
messages = @messages
|
25
|
+
take_snapshot = ->(format = :hash) {
|
26
|
+
snapshot = self.class::Snapshot.new(*(messages.map { |msg| self.send(msg) })).to_h
|
27
|
+
format == :json ? snapshot.to_json : snapshot
|
28
|
+
}
|
29
|
+
define_method(:take_snapshot, &take_snapshot)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
def build_from_snapshot(snapshot_hash, format = :hash)
|
35
|
+
case format
|
36
|
+
when :hash
|
37
|
+
# This line intentionally left blank
|
38
|
+
when :json
|
39
|
+
snapshot_hash = JSON.parse(snapshot_hash).map.with_object({}) do |(k, v), hash|
|
40
|
+
hash[k.to_sym] = v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self::Snapshot.new(snapshot_hash)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/polaroid.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'polaroid'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "polaroid"
|
8
|
+
spec.version = Polaroid::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.authors = ["Paul Kwiatkowski"]
|
11
|
+
spec.email = ["paul@groupraise.com"]
|
12
|
+
spec.summary = %q{Polaroid provides shortcuts to capture the state of a Ruby object, and can construct a fake object later to mimic that state.}
|
13
|
+
spec.description = %q{Polaroid provides shortcuts to capture the state of a Ruby object, and can construct a fake object later to represents that state. The goal is to "Never send a Hash to do an Object's job" when performing common tasks such as, sending data to a background worker as JSON, or otherwise.}
|
14
|
+
spec.homepage = "https://github.com/swifthand/polaroid"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
# spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "immutable_struct", "~> 1.1"
|
26
|
+
spec.add_runtime_dependency "json", "~> 1.8"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: polaroid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Kwiatkowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: immutable_struct
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
69
|
+
description: Polaroid provides shortcuts to capture the state of a Ruby object, and
|
70
|
+
can construct a fake object later to represents that state. The goal is to "Never
|
71
|
+
send a Hash to do an Object's job" when performing common tasks such as, sending
|
72
|
+
data to a background worker as JSON, or otherwise.
|
73
|
+
email:
|
74
|
+
- paul@groupraise.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/polaroid.rb
|
85
|
+
- polaroid.gemspec
|
86
|
+
homepage: https://github.com/swifthand/polaroid
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.2.1
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Polaroid provides shortcuts to capture the state of a Ruby object, and can
|
110
|
+
construct a fake object later to mimic that state.
|
111
|
+
test_files: []
|