immutability 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTQ3MDdmODU2NDZkZmYxMzJjY2E2OTExYTAzMmRlNzY1NmU5NmU4Mw==
5
- data.tar.gz: !binary |-
6
- MTc4MTk4OGVkOWY5NmQzMTNjNGRlMGRmMzk1ZTQwNzdmYzI5MjhhMQ==
2
+ SHA1:
3
+ metadata.gz: 6d2abac3eae8fc1483402fc718dd899c83c929b1
4
+ data.tar.gz: 6dc9066776003a52641d4f2f0fbefd2a75363628
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- Yzc0NTMyOTM1MmY4ZTllOTMyZmE0N2QzOGFhNzg1YjBiZTE2ZmE1ZGQ4OWU1
10
- N2QzMzVmZjQ2NWU0YzUzYmU1YTU3MTM1ZDE3OTU3OTg0NzhlZDIwYTAyN2Yx
11
- NWU4NzM0ZjI5YWQ5MTRkOWIyOGY2MWM5Y2UxZDliZDE2ZWJkYTE=
12
- data.tar.gz: !binary |-
13
- ODg1NDUxY2UzM2VhOGE0M2E2NDQ0ZjlmMzk4NTRiMmM2NzgxN2RjMGM3MTIw
14
- MjdlY2Q4M2NlYzZkMzU5NzE3MzE3MTFmMWYzNWNkNzcxNTEyMjc5ZmUwM2I4
15
- YjM0NmZhODQ3ODA1MDk3MDczYzBjNTQ4ZDRkYzRmMWVmYzFjNDM=
6
+ metadata.gz: 8639bcd519dc339e392c5fc1b0614aeec9a9008a3457cba1c7adb8b24b0e918d3b4c669193b34a74ee945bbf7293869f58def5cb09c2b0530812f1660b6902aa
7
+ data.tar.gz: 4f4e84e41a2933701d3858a3d6293c2b8a74e7e7fc208247c02f67b2d98a2155e1b1db5566801627e98515c203129fd3fe04aca8066e3f92c39051e168daac43
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## v0.0.2 2015-09-24
2
+
3
+ The version fixes some bugs and adds `#at` method fild past states of objects with memory.
4
+
5
+ ### Added
6
+
7
+ - New decorator `Immutabitlity::Object` to iterate via past states of instance (nepalez)
8
+ - New method `Imutability::WithMemory#at` to return a state in the past (nepalez)
9
+ - Allow block to be send to initializer (nepalez)
10
+
11
+ ### Bugs fixed
12
+
13
+ - Bug in rspec matcher `be_immutable` under rbx (nepalez)
14
+
15
+ [Compare v0.0.1...v0.0.2](https://github.com/nepalez/immutability/compare/v0.0.1...v0.0.2)
16
+
1
17
  ## v0.0.1 2015-09-20
2
18
 
3
19
  First public release.
data/README.md CHANGED
@@ -108,6 +108,25 @@ elder_andrew.version # => 1
108
108
  elder_andrew.parent.equal? young_andrew # => true
109
109
  ```
110
110
 
111
+ You can check the previous state of the object using method `#at`:
112
+
113
+ ```ruby
114
+ # relative from the current version
115
+ elder_andrew.at(-2) == nil # => true
116
+ elder_andrew.at(-1) == young_andrew # => true
117
+
118
+ # at some version in the past
119
+ elder_andrew.at(0) == young_andrew # => true
120
+ elder_andrew.at(1) == elder_andrew # => true
121
+ elder_andrew.at(2) == nil # => true
122
+ ```
123
+
124
+ This can be used to check whether two instances has a [cenancestor][cenancestor]:
125
+
126
+ ```ruby
127
+ elder_andrew.at(0) == young_andrew.at(0) # => true
128
+ ```
129
+
111
130
  Notice, than no instances in the sequence can be garbage collected (they still refer to each other).
112
131
 
113
132
  Use `#forget_history` methods to reset version and free old instances for GC:
@@ -187,6 +206,7 @@ See the [MIT LICENSE](LICENSE).
187
206
 
188
207
  [are_we_there_yet]: http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey
189
208
  [aversion]: https://github.com/txus/aversion
209
+ [cenancestor]: https://en.wikipedia.org/wiki/Last_universal_ancestor
190
210
  [codeclimate]: https://codeclimate.com/github/nepalez/immutability
191
211
  [coveralls]: https://coveralls.io/r/nepalez/immutability
192
212
  [gem]: https://rubygems.org/gems/immutability
data/Rakefile CHANGED
@@ -22,12 +22,13 @@ end
22
22
 
23
23
  desc "Runs mutation metric for testing"
24
24
  task :mutant do
25
- system "mutant -r immutability --use rspec Immutability* --fail-fast"
25
+ system "MUTANT=true mutant -r immutability --use rspec Immutability*" \
26
+ " --fail-fast"
26
27
  end
27
28
 
28
29
  desc "Exhort all evils"
29
30
  task :exhort do
30
- system "mutant -r immutability --use rspec Immutability*"
31
+ system "MUTANT=true mutant -r immutability --use rspec Immutability*"
31
32
  end
32
33
 
33
34
  desc "Runs all the necessary metrics before making a commit"
data/lib/immutability.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "ice_nine"
4
4
 
5
+ require_relative "immutability/object"
5
6
  require_relative "immutability/with_memory"
6
7
 
7
8
  # Makes the object immutable (deeply frozen) with possibility to remember
@@ -78,20 +79,13 @@ module Immutability
78
79
  # @api private
79
80
  #
80
81
  # @param [Object, Array<Object>] args
82
+ # @param [Proc] block
81
83
  #
82
84
  # @return [Object]
83
85
  #
84
- def new(*args)
85
- IceNine.deep_freeze __new__(*args)
86
- end
87
-
88
- private
89
-
90
- def __new__(*args, &block)
91
- allocate.tap do |instance|
92
- instance.__send__(:initialize, *args)
93
- instance.instance_eval(&block) if block_given?
94
- end
86
+ def new(*args, &block)
87
+ instance = allocate.tap { |obj| obj.__send__(:initialize, *args, &block) }
88
+ IceNine.deep_freeze(instance)
95
89
  end
96
90
 
97
91
  end # module ClassMethods
@@ -105,14 +99,7 @@ module Immutability
105
99
  # @return [Object] the updated instance
106
100
  #
107
101
  def update(&block)
108
- instance = dup
109
- instance.instance_eval(&block) if block_given?
110
- IceNine.deep_freeze(instance)
111
- end
112
-
113
- # @private
114
- def self.included(klass)
115
- klass.instance_exec(ClassMethods) { |mod| extend(mod) }
102
+ IceNine.deep_freeze dup.tap { |obj| obj.instance_eval(&block) if block }
116
103
  end
117
104
 
118
105
  # Returns the module extended by features to record history
@@ -123,4 +110,10 @@ module Immutability
123
110
  WithMemory
124
111
  end
125
112
 
113
+ private
114
+
115
+ def self.included(klass)
116
+ klass.instance_eval { extend ClassMethods }
117
+ end
118
+
126
119
  end # module Immutability
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module Immutability
3
+
4
+ # Describes the continuous object as a sequence of immutable snapshots
5
+ # with an option of searching the past state of the object
6
+ #
7
+ # @api private
8
+ #
9
+ # @author Andrew Kozin <Andrew.Kozin@gmail.com>
10
+ #
11
+ class Object
12
+
13
+ include Enumerable
14
+
15
+ # Initializes the object from the current state (snapshot)
16
+ #
17
+ # @param [#version, #parent] current
18
+ #
19
+ def initialize(current)
20
+ @current = current
21
+ end
22
+
23
+ # The current (last) version of the object
24
+ #
25
+ # The object knows nothing about its future
26
+ #
27
+ # @return [Integer]
28
+ #
29
+ def version
30
+ @current.version
31
+ end
32
+
33
+ # Iterates via object's snapshots from the current state to the past
34
+ #
35
+ # @return [Enumerator]
36
+ #
37
+ def each
38
+ return to_enum unless block_given?
39
+ state = @current
40
+ while state
41
+ yield(state)
42
+ state = state.parent
43
+ end
44
+ end
45
+
46
+ # Returns the state of the object at some point in the past
47
+ #
48
+ # @param [#to_i] point
49
+ # Either a positive number of target version,
50
+ # or a negative number of version (snapshots) before the current one
51
+ # +0+ stands for the first version.
52
+ #
53
+ # @return [Object, nil]
54
+ #
55
+ def at(point)
56
+ ipoint = point.to_i
57
+ target = (ipoint < 0) ? (version + ipoint) : ipoint
58
+ return unless (0..version).include? target
59
+
60
+ detect { |state| target.equal? state.version }
61
+ end
62
+
63
+ end # class Object
64
+
65
+ end # module Immutability
@@ -4,6 +4,6 @@ module Immutability
4
4
 
5
5
  # The semantic version of the gem.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.1".freeze
7
+ VERSION = "0.0.2".freeze
8
8
 
9
9
  end # module Immutability
@@ -8,39 +8,34 @@ module Immutability
8
8
  #
9
9
  module WithMemory
10
10
 
11
- # @private
12
- class << self
13
- private
11
+ include Immutability
14
12
 
15
- def included(klass)
16
- klass.__send__ :include, Immutability
17
- klass.__send__ :extend, ClassMethods
18
- klass.__send__ :define_method, :update, update
19
- end
13
+ # Methods to be added to class, that included the `Immutability` module
14
+ #
15
+ # @api private
16
+ #
17
+ module ClassMethods
20
18
 
21
- # Redefines the +update+ so that it increment version and refer
22
- # to the previous snapshot of the continuous object
19
+ # Reloads instance's constructor to add version and parent and make
20
+ # the whole instance immutable
21
+ #
22
+ # @api private
23
+ #
24
+ # @param [Object, Array<Object>] args
25
+ # @param [Proc] block
23
26
  #
24
- def update
25
- proc do |&block|
26
- current = [(version + 1), self]
27
- super() do
28
- @version, @parent = current
29
- instance_eval(&block) if block
30
- end
27
+ # @return [Object]
28
+ #
29
+ def new(*args, &block)
30
+ instance = allocate.tap do |obj|
31
+ obj.__send__(:initialize, *args, &block)
32
+ obj.instance_variable_set(:@version, 0)
33
+ obj.instance_variable_set(:@parent, nil)
31
34
  end
35
+ IceNine.deep_freeze(instance)
32
36
  end
33
- end
34
37
 
35
- # Adds version and parent variables to newly created instance
36
- #
37
- module ClassMethods
38
- private
39
-
40
- def __new__(*args)
41
- super(*args) { @version, @parent = 0 }
42
- end
43
- end
38
+ end # module ClassMethods
44
39
 
45
40
  # @!attribute [r] version
46
41
  #
@@ -54,6 +49,19 @@ module Immutability
54
49
  #
55
50
  attr_reader :parent
56
51
 
52
+ # Redefines the +update+ so that it increment version and refer
53
+ # to the previous snapshot of the continuous object
54
+ #
55
+ # @param [Proc] block
56
+ #
57
+ def update(&block)
58
+ current = [version + 1, self]
59
+ super do
60
+ @version, @parent = current
61
+ instance_eval(&block) if block
62
+ end
63
+ end
64
+
57
65
  # Forgets the previous history of the object
58
66
  #
59
67
  # Returns a new instance with the same variables,
@@ -65,6 +73,23 @@ module Immutability
65
73
  update { @version, @parent = 0 }
66
74
  end
67
75
 
76
+ # Returns an ancestor of the instance at some point in the past
77
+ #
78
+ # @param [#to_i] point
79
+ # Either a positive number of version, or a negative number of versions
80
+ # (snapshots) before now. +0+ stands for the first version.
81
+ #
82
+ # @return [Object, nil]
83
+ #
84
+ def at(point)
85
+ Object.new(self).at(point)
86
+ end
87
+
88
+ # @private
89
+ def self.included(klass)
90
+ klass.instance_eval { extend ClassMethods }
91
+ end
92
+
68
93
  end # module WithMemory
69
94
 
70
95
  end # module Immutability
data/spec/shared/user.rb CHANGED
@@ -3,11 +3,12 @@
3
3
  shared_examples :user do
4
4
  before do
5
5
  class User
6
- attr_reader :name, :age
6
+ attr_reader :name, :age, :role
7
7
 
8
8
  def initialize(name, age)
9
9
  @name = name
10
10
  @age = age
11
+ @role = block_given? ? yield : nil
11
12
  end
12
13
  end
13
14
  end
data/spec/spec_helper.rb CHANGED
@@ -16,3 +16,11 @@ require "immutability/rspec"
16
16
 
17
17
  # Loads shared examples
18
18
  require_relative "shared/user"
19
+
20
+ # @todo Remove after resolving of mutant PR#444
21
+ # @see https://github.com/mbj/mutant/issues/444
22
+ if ENV["MUTANT"]
23
+ RSpec.configure do |config|
24
+ config.around { |example| Timeout.timeout(0.5, &example) }
25
+ end
26
+ end
@@ -9,7 +9,7 @@
9
9
  #
10
10
  RSpec::Matchers.define :be_immutable do
11
11
  match do |instance|
12
- if instance.class.respond_to? :new
12
+ if instance && instance.class.respond_to?(:new)
13
13
  expect(instance).to be_frozen
14
14
  instance
15
15
  .instance_variables.map(&instance.method(:instance_variable_get))
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+ describe Immutability::Object do
3
+ let(:object) { described_class.new(instance) }
4
+
5
+ let(:instance) { double :instance, version: 2, parent: parent }
6
+ let(:parent) { double :parent, version: 1, parent: grandparent }
7
+ let(:grandparent) { double :grandparent, version: 0, parent: nil }
8
+
9
+ describe "#version" do
10
+ subject { object.version }
11
+
12
+ it "is delegated to instance" do
13
+ expect(subject).to eql instance.version
14
+ end
15
+ end # describe #version
16
+
17
+ describe "#each" do
18
+ context "without block" do
19
+ subject { object.each }
20
+
21
+ it { is_expected.to be_kind_of Enumerator }
22
+ end
23
+
24
+ context "with a block" do
25
+ subject { object.to_a }
26
+
27
+ it "iterates via ancestors" do
28
+ expect(subject).to eql [instance, parent, grandparent]
29
+ end
30
+ end
31
+ end # describe #each
32
+
33
+ describe "#at" do
34
+ subject { object.at(point) }
35
+
36
+ context "0" do
37
+ let(:point) { 0 }
38
+
39
+ it { is_expected.to eql grandparent }
40
+ end
41
+
42
+ context "positive number less than version" do
43
+ let(:point) { "1" }
44
+
45
+ it { is_expected.to eql parent }
46
+ end
47
+
48
+ context "positive number equal to version" do
49
+ let(:point) { 2 }
50
+
51
+ it { is_expected.to eql instance }
52
+ end
53
+
54
+ context "positive number greater than version" do
55
+ let(:point) { 3 }
56
+
57
+ it { is_expected.to be_nil }
58
+
59
+ it "doesn't check #parent" do
60
+ expect(instance).not_to receive(:parent)
61
+ subject
62
+ end
63
+ end
64
+
65
+ context "negative number less than version" do
66
+ let(:point) { -1 }
67
+
68
+ it { is_expected.to eql parent }
69
+ end
70
+
71
+ context "negative number equal to version" do
72
+ let(:point) { -2 }
73
+
74
+ it { is_expected.to eql grandparent }
75
+ end
76
+
77
+ context "negative number greater than version" do
78
+ let(:point) { -3 }
79
+
80
+ it { is_expected.to be_nil }
81
+
82
+ it "doesn't check #parent" do
83
+ expect(instance).not_to receive(:parent)
84
+ subject
85
+ end
86
+ end
87
+ end # describe #at
88
+
89
+ end # describe Immutability::Object
@@ -4,16 +4,22 @@ describe Immutability::WithMemory do
4
4
  include_context :user
5
5
  before { User.send :include, described_class }
6
6
 
7
- let(:user) { User.new "Andrew", 44 }
7
+ let(:user) { User.new("Andrew", 44) { "admin" } }
8
8
 
9
9
  describe ".new" do
10
10
  subject { user }
11
11
 
12
+ it "calls the initializer" do
13
+ expect(subject.name).to eql "Andrew"
14
+ expect(subject.age).to eql 44
15
+ expect(subject.role).to eql "admin"
16
+ end
17
+
12
18
  it { is_expected.to be_immutable }
13
19
 
14
20
  it "doesn't add hidden variables" do
15
21
  expect(subject.instance_variables).to contain_exactly(
16
- :@name, :@age, :@version, :@parent
22
+ :@name, :@age, :@role, :@version, :@parent
17
23
  )
18
24
  end
19
25
  end # describe .new
@@ -23,7 +29,7 @@ describe Immutability::WithMemory do
23
29
 
24
30
  it "are expected" do
25
31
  expect(subject).to contain_exactly(
26
- :name, :age, :version, :parent, :update, :forget_history
32
+ :name, :age, :role, :version, :parent, :update, :forget_history, :at
27
33
  )
28
34
  end
29
35
  end # describe #methods
@@ -98,4 +104,43 @@ describe Immutability::WithMemory do
98
104
  end
99
105
  end # describe #forget_history
100
106
 
107
+ describe "#at" do
108
+ subject { three.at(point) }
109
+
110
+ let(:zero) { user }
111
+ let(:one) { zero.update }
112
+ let(:two) { one.update }
113
+ let(:three) { two.update }
114
+
115
+ context "0" do
116
+ let(:point) { 0 }
117
+
118
+ it { is_expected.to eql zero }
119
+ end
120
+
121
+ context "positive number less than version" do
122
+ let(:point) { 2 }
123
+
124
+ it { is_expected.to eql two }
125
+ end
126
+
127
+ context "positive number greater than version" do
128
+ let(:point) { 4 }
129
+
130
+ it { is_expected.to be_nil }
131
+ end
132
+
133
+ context "negative number less than version" do
134
+ let(:point) { -2 }
135
+
136
+ it { is_expected.to eql one }
137
+ end
138
+
139
+ context "negative number greater than version" do
140
+ let(:point) { -4 }
141
+
142
+ it { is_expected.to be_nil }
143
+ end
144
+ end # describe #at
145
+
101
146
  end # describe Immutability::WithMemory
@@ -4,7 +4,7 @@ describe Immutability do
4
4
  include_context :user
5
5
  before { User.send :include, described_class }
6
6
 
7
- let(:user) { User.new name, 44 }
7
+ let(:user) { User.new(name, 44) { "admin" } }
8
8
  let(:name) { "Andrew" }
9
9
 
10
10
  describe "::with_memory" do
@@ -16,13 +16,19 @@ describe Immutability do
16
16
  describe ".new" do
17
17
  subject { user }
18
18
 
19
+ it "calls the initializer" do
20
+ expect(subject.name).to eql name
21
+ expect(subject.age).to eql 44
22
+ expect(subject.role).to eql "admin"
23
+ end
24
+
19
25
  it { is_expected.to be_immutable }
20
26
  end # describe .new
21
27
 
22
28
  describe "#methods" do
23
29
  subject { user.methods - Object.instance_methods }
24
30
 
25
- it { is_expected.to contain_exactly(:name, :age, :update) }
31
+ it { is_expected.to contain_exactly(:name, :age, :role, :update) }
26
32
  end # describe #methods
27
33
 
28
34
  describe "#update" do
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immutability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kozin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-20 00:00:00.000000000 Z
11
+ date: 2015-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ice_nine
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.11.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.11.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: hexx-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.5'
41
41
  description:
@@ -46,13 +46,13 @@ extra_rdoc_files:
46
46
  - README.md
47
47
  - LICENSE
48
48
  files:
49
- - .coveralls.yml
50
- - .gitignore
51
- - .metrics
52
- - .rspec
53
- - .rubocop.yml
54
- - .travis.yml
55
- - .yardopts
49
+ - ".coveralls.yml"
50
+ - ".gitignore"
51
+ - ".metrics"
52
+ - ".rspec"
53
+ - ".rubocop.yml"
54
+ - ".travis.yml"
55
+ - ".yardopts"
56
56
  - CHANGELOG.md
57
57
  - Gemfile
58
58
  - Guardfile
@@ -72,12 +72,14 @@ files:
72
72
  - config/metrics/yardstick.yml
73
73
  - immutability.gemspec
74
74
  - lib/immutability.rb
75
+ - lib/immutability/object.rb
75
76
  - lib/immutability/rspec.rb
76
77
  - lib/immutability/version.rb
77
78
  - lib/immutability/with_memory.rb
78
79
  - spec/shared/user.rb
79
80
  - spec/spec_helper.rb
80
81
  - spec/support/immutable.rb
82
+ - spec/unit/immutability/object_spec.rb
81
83
  - spec/unit/immutability/with_memory_spec.rb
82
84
  - spec/unit/immutability_spec.rb
83
85
  homepage: https://github.com/nepalez/immutability
@@ -90,23 +92,24 @@ require_paths:
90
92
  - lib
91
93
  required_ruby_version: !ruby/object:Gem::Requirement
92
94
  requirements:
93
- - - ! '>='
95
+ - - ">="
94
96
  - !ruby/object:Gem::Version
95
97
  version: 1.9.3
96
98
  required_rubygems_version: !ruby/object:Gem::Requirement
97
99
  requirements:
98
- - - ! '>='
100
+ - - ">="
99
101
  - !ruby/object:Gem::Version
100
102
  version: '0'
101
103
  requirements: []
102
104
  rubyforge_project:
103
- rubygems_version: 2.4.6
105
+ rubygems_version: 2.4.8
104
106
  signing_key:
105
107
  specification_version: 4
106
108
  summary: Make instances immutable (deeply frozen) and versioned
107
109
  test_files:
108
110
  - spec/spec_helper.rb
109
111
  - spec/unit/immutability_spec.rb
112
+ - spec/unit/immutability/object_spec.rb
110
113
  - spec/unit/immutability/with_memory_spec.rb
111
114
  - spec/support/immutable.rb
112
115
  - spec/shared/user.rb